Docker Compose at home: one deployment page for a homelab, and the drift between the compose files and the wiki #704

Open
opened 2026-10-03 01:59:13 +00:00 by tiagoagueda · 0 comments
Owner

From the list: “Since it is mostly a homelab oriented project. Improve the documentation on the deployment using docker compose.” The Compose files work, but what an operator needs is spread over five wiki pages and the README, several parts of it contradict the files, and nothing covers what a homelab has: a reverse proxy container, a NAS volume, a stack manager, unattended updates. This issue is the documentation, written after the compose bugs that make it true are fixed.

What exists today

The files (docker/compose.yml, docker/compose.postgres.yml, .env.example, 239 lines) and where the documents disagree with them, each checked on 2026-10-02:

  • “There is no background worker to run. Nothing in Postulo currently queues work.” (Installing-Postulo.md:236), while docker/compose.yml:45-123 defines a scheduler and a worker behind profiles, .env.example:120-128 explains POSTULO_BACKGROUND_WORK, and Configuration.md:337-360 and :408-446 document both. With Docker (Installing-Postulo.md:46-83) mentions neither. The PostgreSQL half of this is #581.
  • The image pin. Every Postulo service names …/postulo:0.2 (docker/compose.yml:29, :50, :93; docker/compose.postgres.yml:23, :51; Installing-Postulo.md:141, :346), #403.
  • Two upgrade paths on one page. Installing-Postulo.md:307-324 says git pull, build --pull, up -d; :346-352 says pull and up -d; :322-324 and :97-98 add re-running fetch_esco after every upgrade (#411 is the open bug that the documented fetch fails in the image).
  • The short version needs a checkout. Installing-Postulo.md:6-12 and README.md:174-182 start with git clone, because the compose file has build: context: .. (compose.yml:23-25) and env_file: ../.env (:31-32). A Compose project pasted into a stack manager has neither.
  • .env.example is written for development. Its first line is “Every variable is optional in development”; POSTULO_DEBUG=true is live (:13) and ignored in the image (src/postulo/config/settings/prod.py:18); POSTULO_ALLOWED_HOSTS=localhost,127.0.0.1 is live (:14) and refuses a real host name until edited; the secret key is commented out (:11); the CSRF origin, needed behind a proxy on a real domain, is commented out (:16); the short version names only two variables to set. Its commented examples POSTULO_MEDIA_ROOT=/var/lib/postulo/media and POSTULO_STATIC_ROOT (:107-108) are not the image's paths (/app/data/media, /app/staticfiles, docker/Dockerfile:153, :156): uncommented in a compose .env, they would put uploads outside the volume.
  • Behind a proxy, prose only. Hardening.md:6-70 explains POSTULO_TRUSTED_PROXIES and names Traefik once, as an anecdote about a rewritten header (:59). There is no Traefik, Caddy or nginx example anywhere in the wiki (Configuration.md:264-265 is the only nginx text, for sending files). The compose files publish 127.0.0.1:8000:8000 and declare no network (compose.yml:38-39), so a proxy container cannot reach it through the host's loopback, which is the case Hardening.md:22 names without showing. The example value 172.18.0.0/16 (.env.example:94) is whichever subnet Docker chose.
  • Storage is one named volume and nothing is said about the alternative. postulo-data at /app/data (compose.yml:37, :125-126) holds the SQLite file, media, plugins, backups and the GeoNames table (docker/Dockerfile:147-155). The image runs as uid 1000 (:324, USER postulo :329), so a bind mount owned by anyone else cannot be written; no wiki page mentions bind mounts, ownership, or SELinux labels. SQLite runs in WAL mode (src/postulo/config/sqlite.py:32-35, :50), and SQLite's own documentation says “WAL does not work over a network filesystem”: /app/data on an NFS or SMB share of a NAS is a trap, and it is the first thing a homelab reaches for.
  • Backups land beside the data. The default archive goes to /app/data/backups on the same volume (Backups-and-your-data.md:156, :253-255); the restore recipe stops postulo scheduler and not worker (:225, #541); the cron lines use /data/stacks/postulo/docker/compose.yml (Backups-and-your-data.md:183, Configuration.md:356), a path nobody has.
  • First-run surprises. The first start downloads GeoNames unasked, from the web container's entrypoint (docker/entrypoint.sh:22-30, #366), which a firewalled homelab sees as a failed fetch; the ESCO step is in the wiki and not in the README quick start; the wiki says createsuperuser is optional and the README lists it.
  • Container name. Troubleshooting.md:17 says docker logs postulo; the compose files set no container_name, so Compose's own naming applies (postulo-postulo-1 for project postulo, service postulo; not run here).
  • Secrets are environment variables only. No *_FILE form exists in config/settings, so Docker or Portainer secrets cannot be used directly.
  • Open deployment issues (cited, not repeated here): #345 (a fuller wiki, 1.0.0), #403, #581, #582 (PostgreSQL password read from docker/.env and pasted into a URL), #540, #541, #542, #584; closed: #219, #234.

Checked against main at f09b6010b and the wiki as it stood with it.

Proposal

  1. One wiki page, “Deploying with Docker Compose”, the operator's path from nothing to a running, backed-up instance, replacing the scattered Docker text (the Installing-Postulo.md sections With Docker, the Docker parts of Upgrading and Releases and upgrading, and the Docker lines in Configuration, Backups and Hardening become links to it). Installing-Postulo.md keeps the non-container path.
  2. A stand-alone Compose example that needs no checkout: image pinned to a minor, env_file beside it, a .env with only what a real deployment needs (key, hosts, CSRF origin, trusted proxies, time zone, mail), the volume or a bind mount with the ownership step, healthchecks, and the scheduler and worker services with the rule that POSTULO_BACKGROUND_WORK and the worker go together.
  3. Reverse-proxy recipes with the Compose network they need: Traefik, Caddy and nginx, each with the proxy and Postulo on one network, the POSTULO_TRUSTED_PROXIES value read from Server settings → Overview, and the header and body-size notes from Hardening.
  4. Storage and platforms: named volume versus bind mount, uid 1000, the network-filesystem warning, and short notes for a Synology or Unraid share, Portainer or another stack manager, each saying what was tested.
  5. First run, upgrade, rollback and backup in Compose terms: generating the key, the first account, what the first start downloads and how to supply the files by hand, pull and up -d after a backup, why there is no downgrade (migrations run on start, docker/entrypoint.sh:10-13; the way back is a restore), a backup copied off the volume, and a cron or systemd timer.
  6. A short “when it does not work” list: the redirect loop of a proxy that is not trusted, a CSRF refusal behind a proxy, an unwritable bind mount, an unhealthy scheduler, the container's name.
  7. Correct each drift above in the same change, and make the README quick start and the wiki's agree.

Decisions

  • One page or edits to five. Recommended: one page, with the others linking to it, so a Compose operator reads one thing; this is the “operator path” #345 asks for in its fuller wiki, scoped to Docker.
  • Where the example Compose file lives. In the wiki only, or as a file in the repository attached to each release (#403 suggests attaching the compose files to a release). Recommended: in the wiki, with a test that holds it to docker/compose.yml, and the release-attachment question left to #403.
  • What is promised for other platforms. The wiki says the image was tested on a Raspberry Pi, and Traefik is the proxy on the maintainer's own instance (Hardening.md:59). Recommended: show the Traefik example as tested, run the Caddy one once before publishing it, and label every other platform's notes “not tested”; do not write instructions nobody has followed.
  • Unattended updates (Watchtower and the like). A pull runs migrations at start and cannot be undone. Recommended: say so, advise updating by hand after a backup, and show how to pin an exact tag if one insists on automation.
  • Secrets. Support *_FILE variables, or only document the environment. Recommended: document the environment now; *_FILE support is its own enhancement if the maintainer wants it.

Builds on

Issues #345, #403, #581, #582, #540, #541, #542, #584, #411, #366, and closed #219, #234. The page is written after #403, #581 and #582 are fixed (0.5.0), or it documents bugs.

Done when

  • The wiki has the page and every claim on it was run once on a Compose host (the Pi or the maintainer's instance); the drift listed above is gone and grep finds no “no background worker” sentence.
  • A test beside tests/test_wiki_surface.py (it skips where the wiki is not checked out beside the repository) parses the page's Compose block as YAML and holds it to docker/compose.yml: the same services, the image tag equal to the minor of pyproject.toml's version, every POSTULO_* name in it one that config/settings reads. tests/test_image_build.py already reads the compose files and gains #403's tag check.
  • The README quick start, .env.example's header and the wiki's short version agree on the first-run steps.
  • No code or record type changes, so the archive format (29 at f09b6010b) and the candidate format (4) stay as they are; the wiki is English first, with translation following #352.

From the list of improvements of 2026-10-02.

From the list: “Since it is mostly a homelab oriented project. Improve the documentation on the deployment using docker compose.” The Compose files work, but what an operator needs is spread over five wiki pages and the README, several parts of it contradict the files, and nothing covers what a homelab has: a reverse proxy container, a NAS volume, a stack manager, unattended updates. This issue is the documentation, written after the compose bugs that make it true are fixed. ## What exists today The files (`docker/compose.yml`, `docker/compose.postgres.yml`, `.env.example`, 239 lines) and where the documents disagree with them, each checked on 2026-10-02: - **“There is no background worker to run. Nothing in Postulo currently queues work.”** (`Installing-Postulo.md:236`), while `docker/compose.yml:45-123` defines a `scheduler` and a `worker` behind profiles, `.env.example:120-128` explains `POSTULO_BACKGROUND_WORK`, and `Configuration.md:337-360` and `:408-446` document both. *With Docker* (`Installing-Postulo.md:46-83`) mentions neither. The PostgreSQL half of this is #581. - **The image pin.** Every Postulo service names `…/postulo:0.2` (`docker/compose.yml:29`, `:50`, `:93`; `docker/compose.postgres.yml:23`, `:51`; `Installing-Postulo.md:141`, `:346`), #403. - **Two upgrade paths on one page.** `Installing-Postulo.md:307-324` says `git pull`, `build --pull`, `up -d`; `:346-352` says `pull` and `up -d`; `:322-324` and `:97-98` add re-running `fetch_esco` after every upgrade (#411 is the open bug that the documented fetch fails in the image). - **The short version needs a checkout.** `Installing-Postulo.md:6-12` and `README.md:174-182` start with `git clone`, because the compose file has `build: context: ..` (`compose.yml:23-25`) and `env_file: ../.env` (`:31-32`). A Compose project pasted into a stack manager has neither. - **`.env.example` is written for development.** Its first line is “Every variable is optional in development”; `POSTULO_DEBUG=true` is live (`:13`) and ignored in the image (`src/postulo/config/settings/prod.py:18`); `POSTULO_ALLOWED_HOSTS=localhost,127.0.0.1` is live (`:14`) and refuses a real host name until edited; the secret key is commented out (`:11`); the CSRF origin, needed behind a proxy on a real domain, is commented out (`:16`); the short version names only two variables to set. Its commented examples `POSTULO_MEDIA_ROOT=/var/lib/postulo/media` and `POSTULO_STATIC_ROOT` (`:107-108`) are not the image's paths (`/app/data/media`, `/app/staticfiles`, `docker/Dockerfile:153`, `:156`): uncommented in a compose `.env`, they would put uploads outside the volume. - **Behind a proxy, prose only.** `Hardening.md:6-70` explains `POSTULO_TRUSTED_PROXIES` and names Traefik once, as an anecdote about a rewritten header (`:59`). There is no Traefik, Caddy or nginx example anywhere in the wiki (`Configuration.md:264-265` is the only nginx text, for sending files). The compose files publish `127.0.0.1:8000:8000` and declare no network (`compose.yml:38-39`), so a proxy *container* cannot reach it through the host's loopback, which is the case `Hardening.md:22` names without showing. The example value `172.18.0.0/16` (`.env.example:94`) is whichever subnet Docker chose. - **Storage is one named volume and nothing is said about the alternative.** `postulo-data` at `/app/data` (`compose.yml:37`, `:125-126`) holds the SQLite file, media, plugins, backups and the GeoNames table (`docker/Dockerfile:147-155`). The image runs as `uid 1000` (`:324`, `USER postulo` `:329`), so a bind mount owned by anyone else cannot be written; no wiki page mentions bind mounts, ownership, or SELinux labels. SQLite runs in WAL mode (`src/postulo/config/sqlite.py:32-35`, `:50`), and SQLite's own documentation says “WAL does not work over a network filesystem”: `/app/data` on an NFS or SMB share of a NAS is a trap, and it is the first thing a homelab reaches for. - **Backups land beside the data.** The default archive goes to `/app/data/backups` on the same volume (`Backups-and-your-data.md:156`, `:253-255`); the restore recipe stops `postulo scheduler` and not `worker` (`:225`, #541); the cron lines use `/data/stacks/postulo/docker/compose.yml` (`Backups-and-your-data.md:183`, `Configuration.md:356`), a path nobody has. - **First-run surprises.** The first start downloads GeoNames unasked, from the web container's entrypoint (`docker/entrypoint.sh:22-30`, #366), which a firewalled homelab sees as a failed fetch; the ESCO step is in the wiki and not in the README quick start; the wiki says `createsuperuser` is optional and the README lists it. - **Container name.** `Troubleshooting.md:17` says `docker logs postulo`; the compose files set no `container_name`, so Compose's own naming applies (`postulo-postulo-1` for project `postulo`, service `postulo`; not run here). - **Secrets are environment variables only.** No `*_FILE` form exists in `config/settings`, so Docker or Portainer secrets cannot be used directly. - **Open deployment issues** (cited, not repeated here): #345 (a fuller wiki, 1.0.0), #403, #581, #582 (PostgreSQL password read from `docker/.env` and pasted into a URL), #540, #541, #542, #584; closed: #219, #234. Checked against `main` at `f09b6010b` and the wiki as it stood with it. ## Proposal 1. **One wiki page, “Deploying with Docker Compose”**, the operator's path from nothing to a running, backed-up instance, replacing the scattered Docker text (the `Installing-Postulo.md` sections *With Docker*, the Docker parts of *Upgrading* and *Releases and upgrading*, and the Docker lines in *Configuration*, *Backups* and *Hardening* become links to it). `Installing-Postulo.md` keeps the non-container path. 2. **A stand-alone Compose example** that needs no checkout: image pinned to a minor, `env_file` beside it, a `.env` with only what a real deployment needs (key, hosts, CSRF origin, trusted proxies, time zone, mail), the volume or a bind mount with the ownership step, healthchecks, and the `scheduler` and `worker` services with the rule that `POSTULO_BACKGROUND_WORK` and the worker go together. 3. **Reverse-proxy recipes** with the Compose network they need: Traefik, Caddy and nginx, each with the proxy and Postulo on one network, the `POSTULO_TRUSTED_PROXIES` value read from *Server settings → Overview*, and the header and body-size notes from *Hardening*. 4. **Storage and platforms**: named volume versus bind mount, uid 1000, the network-filesystem warning, and short notes for a Synology or Unraid share, Portainer or another stack manager, each saying what was tested. 5. **First run, upgrade, rollback and backup in Compose terms**: generating the key, the first account, what the first start downloads and how to supply the files by hand, `pull` and `up -d` after a backup, why there is no downgrade (migrations run on start, `docker/entrypoint.sh:10-13`; the way back is a restore), a backup copied off the volume, and a cron or systemd timer. 6. **A short “when it does not work” list**: the redirect loop of a proxy that is not trusted, a CSRF refusal behind a proxy, an unwritable bind mount, an unhealthy scheduler, the container's name. 7. **Correct each drift above in the same change**, and make the README quick start and the wiki's agree. ## Decisions - **One page or edits to five.** **Recommended: one page, with the others linking to it**, so a Compose operator reads one thing; this is the “operator path” #345 asks for in its fuller wiki, scoped to Docker. - **Where the example Compose file lives.** In the wiki only, or as a file in the repository attached to each release (#403 suggests attaching the compose files to a release). **Recommended: in the wiki, with a test that holds it to `docker/compose.yml`**, and the release-attachment question left to #403. - **What is promised for other platforms.** The wiki says the image was tested on a Raspberry Pi, and Traefik is the proxy on the maintainer's own instance (`Hardening.md:59`). **Recommended: show the Traefik example as tested, run the Caddy one once before publishing it, and label every other platform's notes “not tested”**; do not write instructions nobody has followed. - **Unattended updates** (Watchtower and the like). A pull runs migrations at start and cannot be undone. **Recommended: say so, advise updating by hand after a backup, and show how to pin an exact tag if one insists on automation.** - **Secrets.** Support `*_FILE` variables, or only document the environment. **Recommended: document the environment now**; `*_FILE` support is its own enhancement if the maintainer wants it. ## Builds on Issues #345, #403, #581, #582, #540, #541, #542, #584, #411, #366, and closed #219, #234. The page is written after #403, #581 and #582 are fixed (0.5.0), or it documents bugs. ## Done when - The wiki has the page and every claim on it was run once on a Compose host (the Pi or the maintainer's instance); the drift listed above is gone and `grep` finds no “no background worker” sentence. - A test beside `tests/test_wiki_surface.py` (it skips where the wiki is not checked out beside the repository) parses the page's Compose block as YAML and holds it to `docker/compose.yml`: the same services, the image tag equal to the minor of `pyproject.toml`'s version, every `POSTULO_*` name in it one that `config/settings` reads. `tests/test_image_build.py` already reads the compose files and gains #403's tag check. - The README quick start, `.env.example`'s header and the wiki's short version agree on the first-run steps. - No code or record type changes, so the archive format (29 at `f09b6010b`) and the candidate format (4) stay as they are; the wiki is English first, with translation following #352. From the list of improvements of 2026-10-02.
tiagoagueda added this to the 0.6.0 milestone 2026-10-03 01:59:13 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#704
No description provided.