handbook/docs/deploy.md
disqualifier d3c85acd1e restructure handbook to flat-page spec
Replace the four-section dir layout with the revised flat-page spec:
- Three-page nav: Libraries, Standards, Deploy.
- libraries.md: live client-side fetch of the rethink-public org from the
  Gitea API, denylist-filtered, sorted table linking each repo, no version
  pins, graceful fallback on fetch/CORS failure.
- standards.md: house coding standards.
- deploy.md: deploy guide for rethink-net (services uid 1337, uid-agnostic
  Docker, paths/mounts, bind-mount permissions, read-only secrets).
- index.md landing card grid updated for the three sections.
- gitignore Playwright MCP artifacts.

Verified: mkdocs build --strict clean; libraries fallback renders live.
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 19:48:52 -04:00

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

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:

  • user: "1337:1337" in compose.
  • chmod -R a+rwX /app in the Dockerfile (covers non-mounted dirs — see the bind-mount note below).
  • HOME=/tmp.
  • No in-container user/useradd — don't bake a user into the image.
FROM python:3.12-slim
ENV HOME=/tmp
WORKDIR /app
# git in the build if you pip-install from git
RUN apt-get update && apt-get install -y --no-install-recommends git \
    && rm -rf /var/lib/apt/lists/*
COPY . .
RUN pip install --no-cache-dir . \
    && chmod -R a+rwX /app
CMD ["python", "-m", "yourapp"]
services:
  yourapp:
    build: .
    user: "1337:1337"
    environment:
      HOME: /tmp
    volumes:
      - /srv/configs/<project>:/app/config:ro     # host-managed, read-only
      - /srv/<dev>/<project>:/app/logs            # live + rolled logs
      - yourapp-data:/app/data                     # named volume — the rest

volumes:
  yourapp-data:

Paths and mounts

  • Configs/srv/configs/<project>/ — bind mount, host-managed.
  • Logs/srv/<dev>/<project>/ — bind mount; live and rolled, scraped for monitoring.
  • Everything else (caches, browser profiles, scratch) → named volumes. Docker manages ownership, so there are no host permissions to fiddle with.

Permissions — the bind-mount footgun

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 fall back to console-only, caches re-download every run.

So:

  • Bind-mount sources (configs, logs) must EXIST and be 1337-owned before up. This is handled at provisioning, not a per-deploy chown hook.
  • Named volumes avoid this entirely — use them for anything that doesn't need host visibility.
  • The Dockerfile chmod -R a+rwX /app 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.

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

We do not commit secrets (usually, lol). The rule:

  • Secrets stay gitignored.
  • They're placed on the host at /srv/configs/<project>/.
  • They're bind-mounted read-only at runtime.
  • Never baked into the image — add them to .dockerignore so COPY . . can't grab them.

Rotating a secret = edit the host file and restart. No rebuild.