eratic / docs / 03-deploy.md
828 words

Deploying, and the machine it deploys to

The short version

npm run deploy

Builds all four sites, uploads them, flips a symlink, reloads the web server, and then checks over real HTTPS that every site and every feed returns 200. If that last part fails it tells you, loudly.

To undo:

./deploy/rollback.sh          # previous release
./deploy/rollback.sh --list   # see what is available

Rolling back does not rebuild anything. It moves a symlink, so it takes about as long as SSH takes to connect.

The server

Host 178.104.116.235 (IPv6 2a01:4f8:1c1a:9e3::1)
Provider ird.ro, tier 0x — 2 vCPU, 4 GB, 40 GB
OS Ubuntu 24.04, unattended security upgrades on
Firewall ufw: 22, 80, 443 only
SSH key ~/.ssh/id_ed25519_eratic
Web server Caddy 2 in Docker, owns 80/443, automatic Let's Encrypt

This is not the box the Agora API runs on. That was the original plan and it was the wrong one: Caddy there already owned ports 80 and 443, so eratic would have had to share a config file that Agora's own deploys overwrite. A personal site and a production API also want different things from a server — different reboot tolerance, different appetite for experiments. Separating them costs about 36 credits a month and removes a whole category of problem.

Idle cost of the whole stack: about 18 MB of RAM.

Layout

/opt/web/
  caddy/
    docker-compose.yml
    etc/                       ← mounted at /etc/caddy in the container
      Caddyfile                  server-wide options + import conf.d/*
      conf.d/
        00-placeholder.caddy     keeps the import glob valid on an empty box
        eratic.caddy             this project (deployed from this repo)
  sites/
    eratic/
      releases/<timestamp>-<sha>/{home,blog,fav,io}
      current -> releases/<…>    a RELATIVE symlink
      media/                     video and other large files

Two details that matter and are easy to get wrong:

current is a relative symlink. Caddy mounts /opt/web/sites and resolves the link from inside the container, where /opt/web/... does not exist. An absolute link produces a 404 that looks like a routing problem.

etc/ is mounted as a directory, never file-by-file. Docker pins a single-file bind mount to that file's inode. rsync writes a temp file and renames it, creating a new inode — so the container goes on serving the old config while the host shows the new one, and the reload reports success. This cost an hour; do not undo it.

Adding another domain later

The whole reason for the conf.d layout. For, say, cheri.ro:

  1. Point DNS at 178.104.116.235 (ird dns set cheri.ro @ A 178.104.116.235).
  2. Put the built files in /opt/web/sites/cheri/current/.
  3. Drop a cheri.caddy into /opt/web/caddy/etc/conf.d/.
  4. docker exec web-caddy caddy reload --adapter caddyfile --config /etc/caddy/Caddyfile

Caddy fetches the certificate on the first request. Nothing about eratic changes, and Caddy validates the whole set before loading any of it, so a mistake in the new file cannot take the existing sites down.

What a deploy actually does

  1. Preflight — SSH reachable; notes uncommitted changes and continues.
  2. Build — npm run build, which runs the GPS check on every source photo first. A photograph with coordinates stops the deploy here.
  3. Upload — rsync into releases/<new>/, hard-linking unchanged files against the live release. Publishing one post transfers one post.
  4. Go live — ln -sfn + mv -T, which replaces the symlink atomically. There is no moment where current is missing or partial.
  5. Web server config — deploy/eratic.caddy is uploaded and validated. If Caddy rejects it, the file is removed and the running config is untouched.
  6. Housekeeping — keeps the last 5 releases.
  7. Verify — every site and every feed over HTTPS, with retries, because a brand-new hostname takes a few seconds to get its certificate.

First-time setup

Only needed for a new server:

./deploy/bootstrap.sh

Installs Docker from Docker's signed APT repository, enables unattended upgrades, closes the firewall to everything but 22/80/443, creates the layout, and starts Caddy. Safe to re-run; it only changes what is missing.

Useful things to know

# Watch the web server
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235 \
  'cd /opt/web/caddy && docker compose logs -f'

# What is live right now
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235 \
  'readlink /opt/web/sites/eratic/current'

# Build without uploading anything
npm run deploy -- --dry-run

Every default can be overridden by environment variable: ERATIC_SERVER, ERATIC_SSH_KEY, ERATIC_REMOTE_ROOT, ERATIC_PROJECT, ERATIC_KEEP_RELEASES.

Backups

What is actually irreplaceable:

Everything else — the OS, Docker, the releases — is reproducible from bootstrap.sh and deploy.sh in about ten minutes.

static mirror of HEAD · about · clone: git clone https://git.makefuture.ro/eratic.git