Preparing your app for the host
Everything an app owner needs, from the rules to the first deploy. The checker it mentions downloads from this host too.
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/<you>/<app>/, HTTPS, certificate managed. - 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.
- **
docker-compose.prod.yml** at the repository root, or up to four levels below it. It is the only file the host reads. - No reliance on the project name. The host runs Compose as
<you>--<app>, with any dot in your name turned into_. Aname:in the file is fine and ignored. - **Exactly one published port, on loopback, from
APP_PORT:**
Everything else your services need, they reach over the Compose network by service name.ports: - "127.0.0.1:${APP_PORT}:8080" - 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. - Persistent data in named volumes, declared in the compose file. No bind mounts to host paths for data.
- **Configuration from
.env**, which the host writes next to your compose file, read withenv_file:or${VAR}. Secrets never in the repository. A.env.prod.examplelisting every variable you need, with a comment each. - No host privileges: no
privileged,network_mode: host,pid: host, Docker socket,cap_add,devices, or fixedcontainer_name. - 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. - **Honour
BASE_PATH.** Your app is served under/<you>/<app>, not at the root. ReadBASE_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'snavigate('/')are emitted URLs, and a grep forhref=andfetch(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. - Nothing external: no
external: truevolumes 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 | /<you>/<app> | every URL you build |
APP_ORIGIN | https://apps.pochostsrv01.fls.cloud | CORS allow-list, absolute links |
APP_URL | https://apps.pochostsrv01.fls.cloud/<you>/<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.
const who = req.get('X-Apphost-User'); // Express
const isGuest = req.get('X-Apphost-Kind') === 'guest';
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
healthcheckthat probes127.0.0.1, neverlocalhost. - 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:
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
- Open
https://pochostsrv01.fls.cloud/and sign in with your company Google account. - Applications → Register an app. App name (permanent, lowercase), repository URL, branch, the health path from rule 4, visibility.
- 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.
- 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. - Check. Fetches your branch with the key and runs the contract check. Fix anything red and press it again.
- Deploy. Watch the log. A failed health gate rolls back to the previous version, or stops the app if there was none.
- 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.