Skip to content

Building Images & Deploying

Prerequisites

GitHub CLI

You need the GitHub CLI (gh) installed and authenticated:

gh auth login

GHCR Package Push Access

Your GitHub token must include the write:packages scope to push images to GitHub Container Registry. Add it with:

gh auth refresh -s write:packages

You can verify your scopes with gh auth status.

Docker Buildx

Docker Buildx is required for multi-platform builds. It comes bundled with Docker Desktop.

Tube token

Deployments are triggered via the Tube internal service. You need a TUBE_TOKEN in your .env.prod file (found in 1password):

TUBE_TOKEN=<token>

Building & Pushing Images

Images are built using Docker Bake with the config in docker-bake.hcl. The bake file defines these targets:

Target Dockerfile Description
paperrun-api-base Dockerfile Base image shared by all services
paperrun-api-web Dockerfile.web API server
paperrun-api-celery Dockerfile.celery Celery worker
paperrun-api-mcp Dockerfile.mcp MCP server

Images are tagged with the short commit hash and pushed to ghcr.io/paper-run/paperrun-api/.

Deploying to Production

Full deploy (all services)

just deployprod

This builds all images, pushes them to GHCR, then calls the Tube service to update all services to the new image tag.

Selective deploy (subset of services)

just deployprod-one

Interactively pick which services to deploy from the live Tube list. Other matched prefixes get marked partial-<tag> in GHCR so you can tell them apart from a full release.

Redeploy current images

just redeploy prod

Tells Tube to redeploy the services at their current image tags — useful for picking up an env-var change without building a new image. Only prod is accepted.

Deploying to Staging

just deploystaging

Same flow as deployprod, but the image tag is prefixed with staging- (so prod and staging images are distinguishable in GHCR) and the deploy targets the staging Railway environment via Tube's --staging flag.

The staging Railway environment ID lives alongside the production one in app/scripts/tube.py; the project ID is shared.

Rollback

Rollback returns services to the previous clean image set tracked by Tube — no rebuild, no GHCR push.

just rollbackprod      # roll back production
just rollbackstaging   # roll back staging

Both recipes prompt for confirmation. To preview without mutating Railway, call the script directly with --dry-run:

uv run --env-file .env.prod -m app.scripts.tube rollback --dry-run            # prod
uv run --env-file .env.prod -m app.scripts.tube rollback --staging --dry-run  # staging

Use tube services [--staging] to confirm current tags before and after.

Troubleshooting

403 Forbidden when pushing images

Your token is missing the write:packages scope. Run:

gh auth refresh -s write:packages

invalid reference format in image tag

Make sure git remote get-url origin returns a valid GitHub URL. The deploy commands parse this to build the GHCR registry path.