Insert a new Phase 1 (deployment: Compose + Traefik on a server, marked Done) between the base project and the OTel concepts phase, and shift the old phases 1-10 to 2-11. Update summary, intro, README and CLAUDE.md references (spoiler rules now target Phase 9, the bottleneck hunt). Add docker-compose.traefik.yml: an override that fronts the gateway with Traefik and Let's Encrypt and removes all other published host ports. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
4.2 KiB
Phase 1 — Deployment: Ledgerline on a server behind Traefik
Part of docs/roadmap/ — see intro.md for platform/background context and summary.md for how every phase fits together. Prev: phase-0.md · Next: phase-2.md
Status: Done
Message: Ledgerline is running on a remote server and served over HTTPS at
https://ledgerline.vione.cloud. Verified against the live deployment, not
just the config: the frontend loads, /api/health/all reports every service
ok, and the origin serves a real Let's Encrypt certificate (valid until
2027-01-07, auto-renewed by Traefik).
-
Target: the throwaway server
czy(Ubuntu 24.04, 1 vCPU, 1 GB RAM), repo at/opt/apps/LedgerLine. Docker 29.9 and Compose v5.6 installed from Docker's apt repo. A 2 GB swapfile was added (persisted in/etc/fstab) — nine containers do not fit in 1 GB of RAM without it, and steady state still uses roughly 700 MB plus swap. -
How it's wired:
docker-compose.traefik.ymlis an override, not an edit ofdocker-compose.yml, so local development is unchanged. It adds Traefik v3.6 on ports 80/443 (Docker provider,exposedByDefault=false, HTTP→HTTPS redirect, Let's Encrypt via HTTP-01), routesHost(`ledgerline.vione.cloud`)to the gateway on port 8000, and strips the published host ports from every other service (ports: !reset []) so Postgres, Redis and the services are reachable only on the Compose network. -
No application or
tests/changes. The Dockerfile and base compose file are untouched; schema applied and data seeded on the server with the same steps asmake seed(800 accounts, 250,000 transactions). -
Deploy / redeploy command (always both files — a bare
docker compose upwould use the base file alone and publish every port):docker compose -f docker-compose.yml -f docker-compose.traefik.yml up -d --build -
Cloudflare gotchas hit on the way:
- The first certificate attempt failed with an ACME 404 because the
Cloudflare DNS record did not yet point at the server — a packet capture
on the server showed no inbound traffic from Cloudflare at all. Traefik
only retries issuance on restart, so after fixing the record it needed
docker compose ... restart traefik. - With the orange-cloud proxy on, the SSL/TLS mode must be Full — Flexible combined with Traefik's HTTPS redirect causes a redirect loop.
- The first certificate attempt failed with an ACME 404 because the
Cloudflare DNS record did not yet point at the server — a packet capture
on the server showed no inbound traffic from Cloudflare at all. Traefik
only retries issuance on restart, so after fixing the record it needed
-
Caveats to remember: the deployment is public, including the
/api/admin/chaosendpoints (anyone who finds them can flip flags); internet scanners probed the site within seconds of it going live. The server is disposable by design, and Traefik's state lives in theletsencryptvolume, which is lost on an OS reinstall (a new certificate is simply issued on the next run).
Time-box: 1 evening.
Deliverable: the unmodified-behaviour Ledgerline system reachable at a real domain over HTTPS, reproducible with one compose command.
Concepts
- Traefik's model: entrypoints (the ports it listens on), routers (rule → service), services (where to send traffic), middlewares, and providers (where config comes from — here, Docker labels on containers). Compare with nginx, where the same information lives in one static config file.
- Compose override files.
-f base -f overridemerges the files;!resetclears an inherited key (here,ports) instead of appending to it. - ACME HTTP-01 behind a CDN. Let's Encrypt must be able to reach port 80 on the hostname's actual origin. With Cloudflare proxying, that only works once the DNS record points at the right server.
- Why this phase exists before observability. The telemetry work in
Phases 2–11 is more convincing when the system under observation is a real
deployment rather than a laptop, and the platform (
observability-ansible) is already a remote deployment — Ledgerline now sits on the same footing.
Done when: https://ledgerline.vione.cloud shows the frontend with a valid
certificate; /api/health/all is all ok; only ports 22, 80 and 443 are
reachable on the server; and the deploy is one documented compose command.