WeeForest deployment
June 7, 2026 · View on GitHub
Production runs as a single Docker image (wee-forest-lens) that serves:
/— Astro static site (site/dist)/lens*— Lens map app, tile proxy, area API/weef*— PostHog first-party proxy (shared by site and Lens)
Caddy terminates TLS and reverse-proxies all traffic to the app container.
Quick start
- Copy
.env.exampleto.envand set secrets (MAPBOX_TOKEN, PostHog keys). - Set
IMAGE_TAGin.env— see Image tags below. - Place parquet and mbtiles data under
data/areaanddata/tilesnext to this compose file. - Create the external network once:
docker network create wee_forest_netor use an existing one likecaddy_net - Pull and start:
docker compose pull && docker compose up -d - Add
Caddyfile.wee-forestto your Caddy config and startcaddy-compose.yml.
Image tags
CI tagging:
| Event | Tags pushed | Architectures |
|---|---|---|
| Pull request | mneveroff/wee-forest-lens:<pr-head-sha> only | linux/amd64, linux/arm64 |
Merge to main | <merge-sha> and latest | linux/amd64, linux/arm64 |
Manual dispatch on main | <sha> and latest | linux/amd64, linux/arm64 |
| Manual dispatch elsewhere | <sha> only | linux/amd64, linux/arm64 |
Production host is arm64 (sites-emerald). PR SHA tags published before multi-arch was enabled are amd64-only and will not pull — re-run CI on the PR branch or use a main tag after merge.
docker-compose.yml reads IMAGE_TAG from .env (defaults to latest if unset).
Use a PR's short SHA to test a branch build before merge. After merge, latest tracks main; pin with IMAGE_TAG=<sha> when you need a specific build.
To deploy a specific build:
# in docker/.env
IMAGE_TAG=efe5ba3
docker compose pull wee_forest_lens
docker compose up -d --force-recreate wee_forest_lens
Runtime configuration
Client-side values (Mapbox token, PostHog public key, path prefixes) are not compiled into the JS bundle. The Express server serves /runtime-config.js from the container environment on each request, so the same image can be deployed with different .env files.
Server-only secrets (POSTHOG_API_KEY for area-calculation events) are also read from .env at runtime.
Image build
CI builds the image via the root Dockerfile (multi-stage: Astro + Lens bundle, no secrets required at build time). The image uses the official node:24-trixie base so tileserver's sqlite native bindings load on arm64, and the Docker build includes a tileserver/sqlite smoke test.
CI runs two jobs on every trigger:
| Job | Purpose |
|---|---|
build | Build multi-arch image (no registry push); warms layer cache |
publish | Rebuild from cache and push tags (needs production env) |
Pull requests push <pr-head-sha> only. Merges to main push <sha> and :latest. Manual workflow dispatch from main matches a merge; from other branches it pushes SHA only.
Troubleshooting
/lens/tiles/... returns 504 and logs show ECONNREFUSED to localhost:8080
tileserver-gl is not listening. Check container logs for tileserver-gl exited with code 1.
Common cause on arm64: sqlite3 native bindings linked against a newer glibc than Debian Bookworm-based node:24 provides (GLIBC_2.38 not found). The Dockerfile uses node:24-trixie and runs a build-time tileserver/sqlite smoke test; pull a fresh CI tag and redeploy.
Verify tileserver is up:
docker compose logs wee_forest_lens 2>&1 | grep -E 'tileserver-gl|ECONNREFUSED'