Theory

Prove the Volume — upload, redeploy, download, hash

A deploy that serves a page is not proof. The proof is that your uploads survive a redeploy — the exact thing the Volume exists for. Do it yourself.

1. Register and upload — on the live URL. Open your Railway domain (not localhost), register an account, and upload a document. Note its SHA-256 on the detail page, download it once, and hash the file locally:

$ sha256sum contract.pdf
4a7d1ed414474e4033ac29ccb8653d9b...  contract.pdf

2. Force a redeploy. Change anything trivial and push, or hit Redeploy in the Railway dashboard. This tears down the container and builds a fresh one — the local disk is wiped. Wait for it to go live again.

3. Download the same document. Back on the live site, open the document and Download it again. Hash it:

$ sha256sum contract-after-redeploy.pdf
4a7d1ed414474e4033ac29ccb8653d9b...   # identical

Byte-identical, across a redeploy that wiped the container's disk. The file lived on the Volume at /data/uploads, not the ephemeral container disk. Now imagine this without the Volume: the download would 404, the file gone — and you would have found out in production, from a user. That is the surprise this lesson exists to prevent.

4. Confirm the prod guards are really on. These are the security checks you wrote earlier, now live:

  • The app refuses to boot without its secrets. In the Railway variables, remove SESSION_SECRET and redeploy. The deploy fails — the logs show SESSION_SECRET is required in production and the container never serves. Put it back. No silent insecure default.
  • Secure cookie. In your browser dev tools, look at the signflow_session cookie: Secure and HttpOnly are both set (over http in dev, Secure was not).
  • Strict CSRF is active. Send a POST with no Origin/Referer against the live site:
$ curl -sX POST https://your-app.up.railway.app/login -d 'email=a@b.c&password=x' -o /dev/null -w "%{http_code}\n"
403

Rejected — gorilla/csrf's strict Origin/Referer check is on in prod, because the plaintext bypass was if !IsProd(). In dev, over http, that same request would have passed. The APP_ENV=prod flag turned the whole security posture on, with no code change.

5. Read a real email flow from the logs. Trigger a password reset on the live site. No email arrives — by design: the console sender is the prod default, so the reset link prints to the Railway deploy logs. Copy it from there and finish the reset. You just ran the full email flow on a real deployment with no provider account.

That is a genuinely deployed application: self-migrating, persistent across redeploys, secure by a flag, and fully exercisable with no third-party accounts.