# Running in production

> A deployed system needs durable data, protected secrets and enough logging to diagnose failures without exposing its users

Production differs from your machine: a different filesystem and environment, a
proxy in front of the app, and nobody watching each request. The mechanics of
your Fly access --- your token, the deploy command and the resource limits ---
are on [your hosting access](https://comp.anu.edu.au/courses/comp4020-agentic-coding-studio/topics/hosting-access/). The same production
concerns apply if you change hosting providers.

## The deployment contract

A repeatable deploy requires the complete recipe in the repository and the
credential outside it.

A full-stack repo stores the recipe in three files:

- **`Dockerfile`** --- how source becomes a running image. A Fly deploy is a
  Docker build, so this is the file your choice of stack lives in
- **`fly.toml`** --- the machine, the volume, the port, the environment. This is
  where the app stops being a dev server: one machine, one volume at `/data`,
  `PORT` set, auto-stop when idle
- **`.github/workflows/checks.yml`** --- the checks, then the deploy, then
  probes against the deployed URL

Changing stack means changing the Dockerfile. The workflow and hosting
configuration stay as they are, because neither names a stack. The static half
uses the same separation through `pnpm build` and the `dist/` directory.

A deploy step that ends when the image boots has proved only that the image
boots. The course's CI then checks for a `200` response and valid internal
links, and the lecture demo adds probes for its event stream, an accepted
same-origin POST and a refused cross-site one. Each check covers a production
failure that local checks have missed.

## Data and secrets

Two questions to be able to answer about a deployed app.

**What survives a restart?** On Fly, exactly one thing: the volume. The lecture
demo's SQLite file lives at `/data/app.db` on it, which is why a message posted
before a redeploy is still there afterwards. Everything else --- the container
filesystem, anything held in a module-level variable, the set of open
connections --- is rebuilt from nothing every time the machine starts. Machines
also stop when idle and start on the next request, so "every time" is more often
than you think.

Check this explicitly. An app that writes outside the volume can work for an
afternoon and lose the lot on the next deploy.

**Where do secrets live?** The dividing line in a server-rendered app is which
side of the wire the code runs on. In an Astro page the frontmatter runs on the
server and a `<script>` block is shipped to the browser, so a value read in one
is private and the same value read in the other is public. Anything prefixed
`PUBLIC_` is in the client bundle by design.

Three rules cover the rest:

- an app secret is set on the platform (`flyctl secrets set`) and read as
  `process.env.X` on the server. It is never a literal in the repo
- a deploy credential belongs with the repo but outside git. In this course
  that's `FLY_API_TOKEN` in a gitignored `mise.local.toml`, and in CI it's a
  repository secret
- nothing secret goes in a log line. Logs are the thing you will read in front
  of other people

CI scans every commit in the history for committed credentials, so the cost of
getting this wrong is a red run rather than a quiet leak --- but only for
secrets a scanner can recognise.

## Useful instruments

Choose each instrument for a question you need to answer:

- **is it up, and is it serving what I think?** `flyctl status`, and a request
  to the URL. The deploy probes above are this question asked automatically
- **what did it do when that broke?** `flyctl logs`, which streams the app's
  stdout. Log the things you would want in that moment --- the request path, the
  outcome, the identifier --- and nothing that identifies a person unnecessarily
- **has it stopped working for someone else?** The hardest one, and the reason a
  live app is different from a demo. A form that 403s for every visitor looks
  identical, from your chair, to a form nobody used

A dashboard of every available number does not answer these questions. For an
app this size, a few targeted log lines are enough.
