handbook/docs/deploy.md
disqualifier 8fc81fc4f4 add Gitea-style admonitions, code annotations, callouts
Make the things that matter stand out, matching the Gitea callout look:
- extra.css: brand-orange warning admonitions (#f57c00, the lambda orange),
  hotter danger (#e8590c), blue note/info and cyan tip/example, plus blue
  code-annotation markers.
- deploy.md: footgun -> danger callout, secrets -> orange warning, eligible
  -> tip; Dockerfile and compose gain numbered code annotations explaining
  each magic line; paths/mounts as a table; a restart snippet for rotation.
- workflow.md: warning on setting per-repo git identity before first commit;
  tip elevating verify-by-executing.

Verified in-browser: computed border colors match (warning #f57c00,
danger #e8590c), 4 annotation markers render in brand blue, admonition
icons + tinted headers match the Gitea style. mkdocs build --strict clean.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 20:16:02 -04:00

114 lines
4.1 KiB
Markdown

# Deployment Guide
> Ready for your project to see the light? You may be eligible for deployment on
> **rethink-net** — our fleet of Ubuntu 26.x servers, ready to host whatever
> you've built.
!!! tip "Eligible"
APIs, websites, applets, bots, monitors. The whole network runs on a few
simple, consistent rules — get your container to follow them and deploying is
mostly handing us a `compose.yaml`.
## Docker — the services account
Every service runs containerized as the shared **`services`** account:
**uid/gid 1337**, fixed fleet-wide. Build your image to be **uid-agnostic** so it
runs cleanly as that account.
```dockerfile
FROM python:3.12-slim
ENV HOME=/tmp # (1)!
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/* # (2)!
COPY . .
RUN pip install --no-cache-dir . \
&& chmod -R a+rwX /app # (3)!
CMD ["python", "-m", "yourapp"]
```
1. `HOME=/tmp` — the `services` account has no home dir; anything writing to
`$HOME` (caches, configs) needs a writable target.
2. Install `git` **only if** you `pip install` from git, then clean the apt lists
to keep the image small.
3. `chmod -R a+rwX /app` makes the app tree writable by **any** uid — this is
what "uid-agnostic" means. Note it only covers **non-mounted** dirs (see the
[footgun](#permissions-the-bind-mount-footgun)).
```yaml
services:
yourapp:
build: .
user: "1337:1337" # (1)!
environment:
HOME: /tmp
volumes:
- /srv/configs/<project>:/app/config:ro # (2)!
- /srv/<dev>/<project>:/app/logs # (3)!
- yourapp-data:/app/data # (4)!
volumes:
yourapp-data:
```
1. Run as the shared account. **No** in-container `user`/`useradd` — don't bake a
user into the image; set it here.
2. Configs: host-managed bind mount, mounted **read-only**.
3. Logs: bind mount — live and rolled, scraped for monitoring.
4. Everything else: a **named volume**. Docker owns it, so there are no host
permissions to fiddle with.
## Paths and mounts
| What | Where | How |
| --- | --- | --- |
| Configs | `/srv/configs/<project>/` | bind mount, host-managed |
| Logs | `/srv/<dev>/<project>/` | bind mount; live + rolled, scraped |
| Caches, profiles, scratch | named volume | Docker manages ownership |
## Permissions — the bind-mount footgun
!!! danger "Bind-mount sources must exist and be 1337-owned *before* `up`"
`docker compose up` does **not** create bind-mount directories as you. If a
bind-mount source is missing, the Docker daemon (**root**) creates it **as
root** — and your container (**1337**) then can't write it. Logs silently fall
back to console-only; caches re-download every run.
So:
- **Bind-mount sources (configs, logs) must EXIST and be 1337-owned before `up`**
— handled at provisioning, not a per-deploy chown hook.
- **Named volumes avoid this entirely** — use them for anything that doesn't need
host visibility.
!!! note "Why `chmod a+rwX /app` doesn't save you here"
The Dockerfile `chmod` only covers **non-mounted** dirs. A bind mount
overrides the image directory with the host directory, so for mounted paths
the **host-side ownership wins** — the image's permissions are irrelevant.
## What your compose / Dockerfile needs
- `user: "1337:1337"`
- bind mounts for **configs + logs**
- named volumes for **the rest**
- secrets bind-mounted **`:ro`**
- `HOME=/tmp`
- `chmod -R a+rwX /app`
- `git` in the build if you `pip install` from git
## Secrets
!!! warning "Secrets never go in the image"
We do **not** commit secrets (usually, lol). They stay **gitignored**, live on
the host at `/srv/configs/<project>/`, and are bind-mounted **read-only** at
runtime. Add them to `.dockerignore` so a `COPY . .` can't sweep them into a
layer.
Rotating a secret = edit the host file and restart. No rebuild.
```bash
vim /srv/configs/<project>/secrets.env # edit on the host
docker compose restart yourapp # pick up the change — no rebuild
```