# How apps run on Hatchik

This page is for the AI building the app. It is the contract an app has to meet
to run on Hatchik. Meeting it is the difference between a publish that works
first time and one that fails a health check.

## The contract

- **A `Dockerfile` at the root of the files.** Hatchik builds the app from it.
  Everything else in the file list is the app. Without a root Dockerfile the
  publish is rejected immediately.
- **Listen on the port in the `PORT` environment variable.** Hatchik sets it to
  8000. Read it; do not hard-code it.
- **Bind `0.0.0.0`, never `127.0.0.1` or `localhost`.** An app bound to
  localhost is unreachable from outside its container and every publish will
  fail the health check.
- **Serve `GET /healthz` returning HTTP 200.** Hatchik polls it after every
  publish to decide whether the app came up. No `/healthz`, no successful
  publish.
- **Read `DATABASE_URL` from the environment.** Hatchik injects it, in the shape
  `postgresql://user:pass@postgres:5432/db`. Never hard-code it, never print it,
  and never write code whose purpose is to discover or dump it.
- **Run migrations and seeding at startup.** The host `postgres` resolves only
  inside the app's own network. Nothing outside — not the customer's machine,
  not a script you run locally — can reach the database, so boot-time is the
  only place schema work can happen.
- **Expect the database to be empty on the first boot and populated on every
  boot after.** Migrations must be safe to run repeatedly.
- **Save files in `/data`, and nowhere else.** Publishing builds the app again
  from the files you send, so the app's own filesystem is replaced every single
  time. `/data` is a folder that is kept. A photo somebody uploaded, written to
  `./uploads`; a generated PDF; a SQLite file sitting beside the code — all
  gone, permanently, at the next publish, with nothing to restore them from.
  Write everything the app stores under `/data`, and create the sub-folders you
  want there at startup. **Records belong in Postgres; files belong in
  `/data`.**

Static sites need none of this. A website is files; publish `index.html` and the
rest and they are served.

## What Hatchik guarantees back

- A separate Postgres database for the draft and for the live site. They never
  share data.
- `DATABASE_URL` and `PORT` injected into both.
- If the customer has added an Anthropic or OpenAI key on the project page,
  `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` are injected too. Runtime AI always
  runs on the customer's own key — Hatchik does not sell AI credit. Never ask
  the customer for a key in chat.
- A rebuild of the app on every publish. Two things are left untouched by it:
  the Postgres database, and the `/data` folder. Nothing else inside the app
  survives a publish — that is the whole of the rule, and there is no way to
  keep anything else, because the new app is built from scratch and replaying an
  old copy's files onto it would resurrect what the new version deleted.
- HTTPS on `<name>.hatchik.com`, on `dev.<name>.hatchik.com`, and on any custom
  domain, issued and renewed automatically.
- A lock in front of the draft. `dev.<name>.hatchik.com` answers a "this draft
  is private" page with a 401 to anything that does not carry the project's
  draft key, and so does a custom domain pointed at the draft. Your app never
  sees those requests, and the one path reserved on that address is
  `/__hatchik/` — do not serve anything there. Every deploy to the draft hands
  your AI back an address it can fetch, and hands you a separate link that opens
  the draft in your browser. The live site is public and none of this applies
  to it.

## Every site needs a 404 page

Include a file called `404.html`. It is what somebody sees when they mistype an
address or follow a dead link.

Without one, every address on the site answers with the home page and a success
code. A visitor who mistypes a link sees something that looks like the site
rather than "page not found", and a search engine is told the made-up address
is a real page. The file only has to say the page isn't there and link back to
the home page.

This applies to a simple website as much as to an app.

## What happens when a publish fails

Hatchik starts the new container, polls `/healthz` until it times out, and
watches the restart count for a crash loop. On failure it stops, keeps the
previous version serving, marks the project's status as an error, and returns
the failure **with the last 200 lines of the container's log attached**.

Read those lines before doing anything else. They almost always contain the real
error. Do not guess at a cause, and do not republish the same code hoping for a
different result.

If the fix is not obvious from the logs, `hatchik_logs` returns the current tail
on demand, and `hatchik_undo` puts the last working version back while you work
the problem out.

## Publishing, in order

1. Publish to the draft. That is the default and it is where work belongs.
2. Give the customer the draft link and wait for them to look.
3. When they approve, publish the draft to live — this makes live the exact
   files they previewed, rather than uploading a fresh copy nobody has seen.

Send the whole site every time. A publish carrying a note where the content
should be — "rest of the file unchanged", "truncated for brevity" — is rejected,
and rightly: it would replace a working site with a stub. Ordinary page code is
never the problem; `placeholder="Your name"` on a form box is a normal part of a
real file and always publishes.

## Words to use with the customer

Draft and live. Publish. Go back. Your site, your app, your data. Not: deploy,
environment, container, dev, prod, staging, instance, tier.
