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

4.1 KiB

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.

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).
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.

vim /srv/configs/<project>/secrets.env   # edit on the host
docker compose restart yourapp           # pick up the change — no rebuild