# Preparing your app for the host

For app owners. You keep your repository as it is; the host asks for a short
list of things, checks them for you, and deploys from a web console. This copy
is served by the host at `https://pochostsrv01.fls.cloud/docs/preparing-your-app.md`
with its own addresses filled in.

## What you get

- A public URL: `https://apps.pochostsrv01.fls.cloud/<namespace>/<app>/`, HTTPS, certificate managed.
  The namespace is yours: by default the name part of your address
  (`jane.doe`), or a short one you create on the console's Namespaces page
  (`jdoe`, or `jd-sales` for a group of projects) and pick when you register.
- Access control in front of the app with no code on your side. Pick one
  visibility: *company* (anyone at the company, through Google sign-in), *link*
  (the company, plus people you send an access link), *owner only*, or *public*.
- Deploys from a button or from a push to your branch, with a health gate and
  automatic rollback.
- A page with your containers, health, logs, deploy history, disk and backups.
- Nightly backups of your volumes, and a PostgreSQL dump if you run one.

## What your repository must have

Ten rules. The checker tells you which ones you miss.

1. **`docker-compose.prod.yml`** at the repository root, or up to four levels
   below it. It is the only file the host reads.
2. **No reliance on the project name.** The host runs Compose as
   `<namespace>--<app>`, with any dot turned into `_`. A `name:` in
   the file is fine and ignored.
3. **Exactly one published port, on loopback, from `APP_PORT`:**
   ```yaml
   ports:
     - "127.0.0.1:${APP_PORT}:8080"
   ```
   Everything else your services need, they reach over the Compose network by
   service name.
4. **A real health endpoint**: a path on that port that answers 2xx with JSON or
   plain text when the app works. Not the frontend. A single-page frontend
   answers every path with its `index.html`, and the host refuses an HTML
   answer, so point the host at your backend's endpoint, for example
   `/api/health`. Name it when you register, or leave the field blank: at the
   first deploy the host tries the routes in your repository and the usual
   names, keeps the first that answers properly, and records it.
5. **Persistent data in named volumes**, declared in the compose file. No bind
   mounts to host paths for data.
6. **Configuration from `.env`**, which the host writes next to your compose
   file, read with `env_file:` or `${VAR}`. Secrets never in the repository. A
   `.env.prod.example` listing every variable you need, with a comment each.
7. **No host privileges**: no `privileged`, `network_mode: host`, `pid: host`,
   Docker socket, `cap_add`, `devices`, or fixed `container_name`.
8. **Safe restarts**: `restart: unless-stopped`; migrations on boot or in a
   one-shot service that exits 0. The host restarts you on every deploy and
   reboots occasionally.
9. **Honour `BASE_PATH`.** Your app is served under `/<namespace>/<app>`, not at the
   root. Read `BASE_PATH` (for example `/jane.doe/invoices`) and build every
   URL you emit from it: the frontend's asset base, the router's base, the API
   prefix the frontend calls, redirects, cookie paths. Client-side navigation
   counts too: `location.replace('/')`, `location.href = '/login'` and a
   router's `navigate('/')` are emitted URLs, and a grep for `href=` and `fetch(`
   will not find them. Default it to empty so local runs and other hosts are
   unchanged. If the app is a third-party image
   you cannot change, tick *strip prefix* at registration; links the app emits
   are still wrong in that mode, so it is the inferior option.
10. **Nothing external**: no `external: true` volumes or networks.

### Variables the host sets for you

Never ask anyone to type these, and default your own names to them:

| Variable | Value | Use it for |
|---|---|---|
| `APP_PORT` | the loopback port | the published port in compose |
| `BASE_PATH` | `/<namespace>/<app>` | every URL you build |
| `APP_ORIGIN` | `https://apps.pochostsrv01.fls.cloud` | CORS allow-list, absolute links |
| `APP_URL` | `https://apps.pochostsrv01.fls.cloud/<namespace>/<app>/` | links in emails, OAuth callbacks |

In compose, a name of your own can default to one of them:
`CORS_ORIGIN: ${CORS_ORIGIN:-${APP_ORIGIN}}`.

### Who is visiting

When the visibility is anything but *public*, every request that reaches your
app carries two headers set by the host:

| Header | A signed-in colleague | A visitor with an access link |
|---|---|---|
| `X-Apphost-User` | their email address | the link's label, for example `Acme pilot` |
| `X-Apphost-Kind` | `user` | `guest` |

Use them to greet the visitor, hide admin screens from guests, or record who
did what. They cannot be forged on the host: the proxy drops any copy a visitor
sends, and your app is reachable only through the proxy. On a *public* app they
are absent. Locally there is no proxy, so take a development identity from a
setting, never from the request.

```js
const who = req.get('X-Apphost-User');           // Express
const isGuest = req.get('X-Apphost-Kind') === 'guest';
```

```python
who = request.headers.get("X-Apphost-User")        # Flask
is_guest = request.headers.get("X-Apphost-Kind") == "guest"
```

### Sharing a demo with a client

Set the visibility to *link*, then on the app's **Access** tab make one link
per client, with a label and an expiry. The client clicks it once; their browser
stays signed in until the link expires or you revoke it, and the plain address
then works as a bookmark. Revoking cuts them off on their next click. The link
is shown once, when you make it; copy it then.

### Recommended

- A compose `healthcheck` that probes `127.0.0.1`, never `localhost`.
- A boot-time assertion that refuses to start with a debug or bypass flag set.
- Defaults in compose for variables that have a sensible one
  (`${MODEL:-gpt-4o}`), so the console does not ask for them.

## Check before you register

With Docker on your laptop. Download the checker from the host, then run it on your repository:

```bash
curl -fsSLo apphost-check-repo https://pochostsrv01.fls.cloud/docs/apphost-check-repo
chmod +x apphost-check-repo
./apphost-check-repo /path/to/your/app
```

A ✗ is a rule you miss; a ! is advice. The host runs the same check before
every deploy.

## Onboarding, in order

1. Open `https://pochostsrv01.fls.cloud/` and sign in with your company Google account.
2. **Applications → Register an app.** Namespace and app name (both permanent, lowercase),
   repository URL, branch, the health path from rule 4, visibility.
3. The app page shows a **deploy key**. Add it to your repository: Settings →
   Deploy keys → Add, read-only. Nobody's personal GitHub access is involved.
4. **Environment:** the page lists every variable your app declares. Fill the
   ones without a default and save. Values are written once and never shown
   again. Any character is kept exactly as typed, `$` and quotes included,
   so a generated password needs no escaping; only line breaks are refused.
5. **Check.** Fetches your branch with the key and runs the contract check.
   Fix anything red and press it again.
6. **Deploy.** Watch the log. A failed health gate rolls back to the previous
   version, or stops the app if there was none.
7. Optional, push to deploy: GitHub → Settings → Webhooks, the payload URL and
   secret from the app page, content type `application/json`, event *push*.

## When something fails

| Symptom | Cause | Fix |
|---|---|---|
| Health gate: "200 but text/html" | the health path hits the frontend's catch-all | set the path to your backend's endpoint in Settings |
| Health gate: connection refused or 5xx | the app did not start, or listens on another port | container logs on the app page; the published port must be the one the app listens on |
| Build fails on a missing variable | compose interpolates a variable that is not set | fill it under Environment, or give it a default in compose |
| Page loads, assets or API calls 404 | absolute paths that ignore the prefix | rule 9; as a stopgap, *strip prefix* in Settings |
| Login or OAuth comes back to the wrong place | callback URL without the prefix | build it from `APP_URL` |

## Limits to know about

- The host is shared. Each app gets a CPU and memory limit set by the host;
  images are built on the host, so keep builds reasonable.
- One port per app; everything public goes through the proxy on 443.
- All apps share one hostname, so do not put real client data in a demo: a
  hole in one app on the host can be used against the sessions of another.
- Local backups are kept 30 days; ask your administrator about off-site copies.
- An app nobody has deployed for 90 days is parked: its containers stop and its
  address shows a page saying the service is switched off. Nothing is deleted.
  You get an email and a Slack message two weeks and three days ahead, and the
  dashboard warns you too. Any deploy, or the Keep button on the app page,
  resets the clock; Unpark or Deploy brings a parked app back. You can also park
  an app yourself when a demo is over.
