When something is wrong
Roughly in the order things actually go wrong.
The build refuses to run
✗ N image(s) carry GPS coordinates
Working as intended — a photograph would have published your location. Strip
location on export from your photo editor (lossless), or:
See docs/06-privacy.md.
Alt text is required / summary errors
Schema validation. The message names the file and the field. Every photograph
needs alt; every entry needs summary. Both are load-bearing — see
CLAUDE.md.
ImageNotFound for a photo you just deleted
The content store outlives the files. It lives in
sites/<site>/node_modules/.astro/data-store.json — not the .astro
directory next to it, which is the one you would think to delete.
covers both, plus dist/. This bites every time you remove an entry that had
images.
The collection "posts" does not exist or is empty
A warning, not an error, and correct — the collection really is empty. It goes
away with the first entry.
Cannot find module '@eratic/core/...'
Usually a fresh clone with no node_modules, or a new subpath that is not in
the package's exports map. .astro imports resolve through the wildcard
because they carry an extension; a bare .ts subpath needs an explicit entry in
packages/core/package.json.
A change does not appear on the site
Check what is actually live:
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235 \
'readlink /opt/web/sites/eratic/current'
If that is the release you just deployed, and the page still looks old, it is a
browser cache. HTML is served must-revalidate, so a hard reload settles it.
If the entry is missing entirely: it is draft: true, or its pubDate is in
the future. Both are excluded from production builds and visible in npm run
dev. That is the intended staging mechanism.
A site returns 404 for everything
Almost always the symlink.
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235 \
'ls -la /opt/web/sites/eratic/current; docker exec web-caddy ls /srv/eratic/current/'
current must be relative (releases/2026…, not /opt/web/...). Caddy
resolves it from inside the container, where the absolute path does not exist.
deploy.sh creates it correctly; a hand-made link is the usual culprit.
Caddy config changes appear to do nothing
The reload says it succeeded and behaviour does not change.
Confirm the container can see the file:
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235 \
'diff <(docker exec web-caddy cat /etc/caddy/Caddyfile) /opt/web/caddy/etc/Caddyfile'
If they differ, something is bind-mounting a file instead of a directory.
Docker pins single-file mounts to an inode; rsync and most editors replace
files by rename, creating a new one. The container then serves the old contents
forever. /opt/web/caddy/etc is mounted as a directory precisely to avoid
this — see deploy/server/docker-compose.yml.
TLS certificate problems
Caddy fetches certificates on the first request for a hostname. A fresh
hostname can take a few seconds; deploy.sh retries five times.
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235 \
'docker logs web-caddy 2>&1 | grep -i -E "certificate|acme|error" | tail -20'
Most failures are DNS: the name must resolve to 178.104.116.235 before
Caddy asks, because Let's Encrypt validates over HTTP.
dig +short blog.eratic.ro @ns1.ird.ro
Let's Encrypt rate-limits repeated failures for the same name. If you have been
retrying, wait an hour rather than looping.
Rolling back
./deploy/rollback.sh --list
./deploy/rollback.sh # previous release
./deploy/rollback.sh 20260805-110627-6cd485e # a specific one
Seconds, no rebuild. The last 5 releases are kept.
The server itself
ssh -i ~/.ssh/id_ed25519_eratic root@178.104.116.235
docker ps # caddy should be Up
cd /opt/web/caddy && docker compose logs --tail 50
df -h / # releases are hard-linked; disk is rarely the issue
free -m # the whole stack idles around 18 MB
ufw status # 22, 80, 443 only
Restart the web server:
cd /opt/web/caddy && docker compose restart
Rebuilding the server from nothing
If the box is lost entirely:
ird vps create web --tier 0x --image ubuntu-24.04 --ssh-key <id>
ird dns set eratic.ro @ A <new-ip> # and www, blog, fav, io, media
# update SERVER in deploy/bootstrap.sh and deploy/deploy.sh
./deploy/bootstrap.sh
npm run deploy
About ten minutes, most of it waiting for apt. Everything except the contents
of media/ is reproducible from this repository — which is why media/ is the
one directory that needs a backup of its own.