Commit Graph

4 Commits

Author SHA1 Message Date
f6d5d18f0d Fix media volume permissions for non-root container user
/app/media didn't exist in the image, so the media_data volume had
nothing to copy ownership from on first mount — Docker created the
mount point owned by root, and the container runs as rosterchief.
Uploads then failed with PermissionError. Create the directory
before the chown so it carries the right ownership into the volume.
2026-08-06 21:58:09 +02:00
5b51f2c945 Point HOME at /app so gunicorn stops erroring on boot
gunicorn 26's control server creates a socket in $HOME. The app user has no home
directory, so every boot logged "Permission denied: /home/rosterchief" — harmless
but noisy. /app is the workdir and already owned by the app user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 13:18:10 +02:00
5d42691a10 Fix the image build: fetch the git dependency in its own stage
The build died at `uv sync`: django-lucide is our fork, declared as a git source
and pinned by the lock to a commit, so uv shells out to `git` to fetch it — and
python:3.14-slim has no git.

Installing git in the runtime image would have fixed it and left a build tool, plus
its dependency tree, in production for the sake of one package that is already
vendored into the venv by then. So the virtualenv is now built in a stage that has
git, and the finished .venv is copied into a runtime stage that does not. Same base
image, so the compiled wheels inside it stay ABI compatible.

Also drops the second `uv sync`, which installed the project itself: there is no
[build-system] and rosterchief is not a package — gunicorn imports it from the
working directory, exactly as it does locally.

Unverified end to end: still no container runtime on this machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 18:07:54 +02:00
35d1ec45a7 Containerise: Dockerfile, Compose stack and wildcard TLS
One server now, the same image and env vars for many later: point
DJANGO_DATABASE_URL / DJANGO_REDIS_URL at central services, set a bucket, drop the
db and redis services, run several web containers behind a load balancer. No code
changes.

The wildcard certificate is what shapes this. Subdomain tenancy needs
*.rosterchief.app, and Let's Encrypt will not issue a wildcard over HTTP-01 -- only
DNS-01 -- so Caddy is built with a DNS provider plugin and needs an API token. That
single constraint is why the proxy is Caddy rather than the usual nginx+certbot.

The image apt-installs libpango and friends, which is what WeasyPrint binds to. The
PDF invoices that cannot render on a Mac without Homebrew work in the container by
construction.

Migrations are NOT run by the entrypoint: with more than one web container they
would race, and a starting gunicorn worker is a bad place to discover a failed
migration. Deploy runs them once, explicitly.

Two things the local build check caught, either of which would have failed the
image build at collectstatic (manifest storage treats a missing referenced file as
fatal):

- chart.js ended with a sourceMappingURL pointing at a .map we never vendored.
  Stripped, with an npm script so re-vendoring cannot bring it back.
- The Tailwind INPUT file lived at static/src/app.css, inside the served static
  tree, so collectstatic collected it and then choked on its @import "tailwindcss".
  It belongs outside: it is a build input, not an asset. Now assets/app.css.

Verified locally under gunicorn + WhiteNoise + manifest storage: pages serve and
the CSS comes back hashed. The image itself is unverified -- there is no container
runtime on this machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:41:51 +02:00