The deploy files and the Railway CLI
Four files as tabs — the container, the ignore list, the Railway config, and the env template. Dockerfile → .dockerignore → railway.json → .env.example.
1. Dockerfile (new) — the multi-stage build. Stage one (golang:1.26) downloads modules on their own cached layer, copies the source, and go builds a static binary (CGO_ENABLED=0, -trimpath -ldflags="-s -w" for a smaller, reproducible result). Stage two is distroless/static-debian12 — it carries CA certificates (needed for the Resend HTTPS call) and nothing else. The final image is just COPY --from=build of the binary. templ/sqlc output is committed, so the build is a plain go build with no code-gen step.
2. .dockerignore (new) — keeps the build context small and secrets out of the image: .git/, uploads/, *.exe, .env, *.md. Never ship your local .env or your local uploads into an image.
3. railway.json (new) — selects the DOCKERFILE builder, points at Dockerfile, and configures the deploy: /healthz healthcheck, restart on failure. Railway reads this on every deploy.
4. .env.example (new) — the documented surface of every variable, committed so a new developer copies it to .env. It spells out which vars are required in prod (SESSION_SECRET, BASE_URL) and flags the ephemeral-filesystem note on UPLOAD_DIR — the decision this lesson resolves.
Deploy it (Railway CLI — a free account is enough):
railway login
railway init --name signflow
railway add --database postgres # provides DATABASE_URL
# Deploy the service with prod config. SESSION_SECRET is generated on the spot.
railway add --service signflow \
--variables APP_ENV=prod \
--variables 'DATABASE_URL=${{Postgres.DATABASE_URL}}' \
--variables UPLOAD_DIR=/data/uploads \
--variables "SESSION_SECRET=$(openssl rand -base64 32)"
railway up # build the Dockerfile and deploy
railway domain # get your public URL
Then, in the Railway dashboard, add a Volume to the service mounted at /data (so UPLOAD_DIR=/data/uploads lands on persistent disk), and set BASE_URL to the domain Railway gave you (it goes into email links). Redeploy.
On boot the binary runs its migrations against the Postgres addon and starts serving. Watch the deploy logs:
INFO starting signflow env=prod port=8080 base_url=https://signflow-....up.railway.app
INFO goose msg="OK 00001_users.sql"
...
INFO file storage: local disk dir=/data/uploads
INFO listening addr=:8080
/healthz goes green and the URL is live.