Files
Movses AslanyanandClaude Opus 5.5 34a11da848 Document deploy.sh and where the observability platform lives
phase-1: deploy via deploy.sh, correct the container count, and note
that stripped host ports leave no path for the Phase 5 scrape.
phase-2: explain why Grafana is not on the Ledgerline server and why
Cloudflare's proxy never forwards :3000 or the other platform ports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:01:49 +03:30

86 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 1 — Deployment: Ledgerline on a server behind Traefik
*Part of `docs/roadmap/` — see [`intro.md`](intro.md) for platform/background context and [`summary.md`](summary.md) for how every phase fits together. Prev: [`phase-0.md`](phase-0.md) · Next: [`phase-2.md`](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`) —
ten containers (nine Ledgerline + Traefik) do not fit in 1 GB of RAM without
it, and steady state still uses roughly 600–700 MB plus swap. There is no
headroom for the observability platform on this host.
- **How it's wired:** `docker-compose.traefik.yml` is an *override*, not an
edit of `docker-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), routes
``Host(`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 as `make seed` (800 accounts, 250,000 transactions).
- **Deploy / redeploy:** run `./deploy.sh` from your own machine. It SSHes to
`czy` (override with `DEPLOY_HOST` / `DEPLOY_DIR`), does `git pull --ff-only`
in `/opt/apps/LedgerLine`, and runs the compose command below. The server
pulls from the remote, so push first; the script warns about unpushed
commits. The underlying command, if run by hand on the server (always both
files — a bare `docker compose up` would use the base file alone and
publish every port):
```bash
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.
- **Caveats to remember:** the deployment is public, including the
`/api/admin/chaos` endpoints (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 the
`letsencrypt` volume, which is lost on an OS reinstall (a new certificate is
simply issued on the next run).
- **Consequence for later phases:** because `!reset` strips every service's
host port, nothing outside `czy` can reach a service's `/metrics` directly.
The Phase 5 scrape (Prometheus on the observability host →
`prometheus_extra_scrape_jobs`) needs a deliberate path in — e.g. a
firewalled port or a Traefik route — decided before Phase 5.
**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 override` merges the files;
`!reset` clears 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.