ethercalc
Version:
Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)
110 lines (105 loc) • 4.48 kB
YAML
# EtherCalc self-host compose file (§13 Q5).
#
# Default service — the standalone workerd Worker. No Redis: room state
# lives in Durable Object SQLite files under the `./ethercalc-data`
# bind-mounted at /data in the container. `docker compose up -d` is the
# whole install story.
#
# Migration profile — activated with `--profile migrate`, adds two
# short-lived services (`legacy-redis` + `migrator`) that ingest a
# legacy Redis dump.rdb into the new Worker in one shot. Driven by
# `bin/migrate-legacy.sh`; end users should not invoke the profile
# directly.
services:
ethercalc:
build:
context: .
dockerfile: Dockerfile
image: ethercalc:selfhost
container_name: ethercalc
restart: unless-stopped
ports:
# Host 8000 matches the legacy port from the old compose file.
- "8000:8000"
volumes:
# Persistent Durable Object state. Survives container restarts.
# Created on first `up` if missing.
- ./ethercalc-data:/data
environment:
# All variables are optional; sensible defaults live in the image.
# Pass through from the host so `ETHERCALC_KEY=secret docker compose
# up -d` Just Works without editing this file.
ETHERCALC_PORT: "${ETHERCALC_PORT:-8000}"
ETHERCALC_HOST: "${ETHERCALC_HOST:-0.0.0.0}"
ETHERCALC_KEY: "${ETHERCALC_KEY:-}"
ETHERCALC_DISABLE_ROOM_INDEX: "${ETHERCALC_DISABLE_ROOM_INDEX:-1}"
ETHERCALC_CORS: "${ETHERCALC_CORS:-}"
ETHERCALC_BASEPATH: "${ETHERCALC_BASEPATH:-}"
ETHERCALC_EXPIRE: "${ETHERCALC_EXPIRE:-}"
# Always forwarded; the migrate endpoint returns 404 when unset
# so leaving it blank keeps the route invisible in normal runs.
ETHERCALC_MIGRATE_TOKEN: "${ETHERCALC_MIGRATE_TOKEN:-}"
# Health check lets the migrator service wait until /_health is
# answering before firing the first seed PUT.
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8000/_health >/dev/null || exit 1"]
interval: 2s
timeout: 2s
retries: 60
start_period: 5s
# ────────────────────────── migration profile ──────────────────────────
# Activated with `docker compose --profile migrate up`. Typical entry
# point is `./bin/migrate-legacy.sh`, which wires the dump file, token,
# and post-run backup in one go.
legacy-redis:
profiles: ["migrate"]
image: redis:7-alpine
container_name: ethercalc-legacy-redis
# Redis's upstream entrypoint chowns /data/* at startup, which
# fails against a read-only bind mount. Bind the user's dump to
# an /input/ stage instead, copy it into the container-owned
# /data at boot, then exec redis. Side effect: peak disk use
# during migration is 2× the dump size (acceptable — migration
# is transient, and the host copy is preserved untouched).
entrypoint: ["/bin/sh", "-c"]
command:
- 'cp /input/dump.rdb /data/dump.rdb && exec redis-server --dir /data --dbfilename dump.rdb --save "" --appendonly no'
volumes:
# `ETHERCALC_LEGACY_DUMP` is set by `bin/migrate-legacy.sh` to
# the absolute path of the user's dump.rdb. `:ro` guarantees the
# source file is never modified even if something in the stack
# misbehaves.
- "${ETHERCALC_LEGACY_DUMP:-./legacy-dump.rdb}:/input/dump.rdb:ro"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 2s
timeout: 2s
retries: 60
start_period: 5s
migrator:
profiles: ["migrate"]
image: ethercalc:selfhost
container_name: ethercalc-migrator
# Same image as the worker — bun + bin/ethercalc are already there.
# One-shot: `ethercalc migrate`, then exit. `--abort-on-container-
# exit` + `--exit-code-from migrator` on `docker compose up` makes
# the wrapper script fail loudly if the migrator itself fails.
depends_on:
legacy-redis:
condition: service_healthy
ethercalc:
condition: service_healthy
environment:
ETHERCALC_MIGRATE_TOKEN: "${ETHERCALC_MIGRATE_TOKEN:-}"
entrypoint: ["/app/bin/ethercalc"]
command:
- "migrate"
- "--source"
- "redis://legacy-redis:6379"
- "--target"
- "http://ethercalc:8000"
- "--token"
- "${ETHERCALC_MIGRATE_TOKEN:-}"
- "--concurrency"
- "${ETHERCALC_MIGRATE_CONCURRENCY:-8}"
restart: "no"