Skip to content

Docker Deployment

Craft runs as a set of Docker containers orchestrated by Docker Compose. There are two profiles — dev for local development with hot reload, and prod for production with built images and no volume mounts.

The CRAFT_MODE variable in your .env does not control which containers run — you choose the profile explicitly with make dev or make prod (or docker compose --profile dev/prod).

  • api-dev — FastAPI with source mounted from packages/api/src, auto-reload on save
  • web-dev — Astro dev server with HMR, source mounted from packages/web/src
  • docs-dev — Starlight dev server with HMR

Volume mounts mean you edit files on the host and see changes immediately.

Both profiles share the same infrastructure containers (no profile required):

  • db — PostgreSQL 17 (TimescaleDB HA image) with pgvector and pgai extensions
  • pgai-install — One-shot container that installs the pgai extension, then exits
  • vectorizer-worker — Continuously generates embeddings for new or updated catalog objects
  1. Clone the repository

    Terminal window
    git clone git@git.supported.systems:warehack.ing/astrolock.git ~/astrolock
    cd ~/astrolock
  2. Create your .env from the template

    Terminal window
    cp .env.example .env

    Edit .env and set at minimum:

    Terminal window
    CRAFT_MODE=prod
    CRAFT_DOMAIN=space.your-domain.com
    POSTGRES_PASSWORD=a-strong-password
    GPU_API_KEY=your-embedding-api-key

    If you have Space-Track credentials for supplementary TLE data:

    Terminal window
    SPACETRACK_USER=your-username
    SPACETRACK_PASS=your-password
  3. Build and start the containers

    Terminal window
    make prod

    Or equivalently:

    Terminal window
    docker compose --profile prod up --build -d
  4. Run database migrations

    Terminal window
    docker compose exec api-prod alembic upgrade head
  5. Seed the catalog (TLEs, celestial objects, radio frequencies)

    Terminal window
    docker compose exec api-prod python -m astrolock_api.seed
  6. Backfill search text for embeddings

    Terminal window
    docker compose exec api-prod python -m astrolock_api.backfill_search_text
  7. Trigger initial launch data fetch

    Terminal window
    docker compose exec api-prod python -c "
    import urllib.request, json
    req = urllib.request.Request('http://127.0.0.1:8000/api/launches/refresh', method='POST')
    resp = urllib.request.urlopen(req)
    print(json.loads(resp.read()))
    "
  8. Verify the deployment

    Terminal window
    curl https://space.your-domain.com/api/health
    # {"status": "ok", "service": "craft-api"}
    curl https://space.your-domain.com/api/satellites/25544
Terminal window
cd ~/astrolock
git pull
docker compose --profile prod up --build -d api-prod web-prod docs-prod

If there are new database migrations:

Terminal window
docker compose exec api-prod alembic upgrade head

Craft uses caddy-docker-proxy for TLS termination and routing. The Docker Compose labels on each service container tell Caddy how to route traffic:

  • /api/* and /ws/* route to the API container on port 8000
  • /docs/* routes to the docs container on port 3000
  • Everything else routes to the web container on port 4321

WebSocket paths (/ws/*) have special proxy settings to prevent Caddy from closing idle connections:

caddy.handle_1.0_reverse_proxy.flush_interval: "-1"
caddy.handle_1.0_reverse_proxy.transport.read_timeout: "0"
caddy.handle_1.0_reverse_proxy.transport.write_timeout: "0"

TLS is automatic via DNS challenge (ACME + your DNS provider’s API). When you deploy with a new subdomain, expect up to about 2 minutes for Let’s Encrypt to issue the certificate. During that window, HTTPS requests will fail with TLS errors — no manual intervention is needed, just wait.

After seeding, the vectorizer worker processes all catalog objects to generate embeddings. For 22,000+ objects, this takes roughly 10 minutes. Check progress:

Terminal window
docker compose exec db psql -U astrolock -d astrolock \
-c "SELECT * FROM ai.vectorizer_status;"

Verify that all embedding stores are populated:

Terminal window
docker compose exec db psql -U astrolock -d astrolock -c "
SELECT 'satellite' as type, COUNT(*) FROM satellite_embedding_store
UNION ALL SELECT 'celestial', COUNT(*) FROM celestial_object_embedding_store
UNION ALL SELECT 'frequency', COUNT(*) FROM target_frequency_embedding_store
UNION ALL SELECT 'launch', COUNT(*) FROM rocket_launch_embedding_store;
"
Terminal window
# All containers
docker compose logs -f
# API only
docker compose logs api-prod --tail 50 -f
# Vectorizer worker
docker compose logs vectorizer-worker --tail 50 -f

The API exposes health endpoints at both /health (internal, for Docker healthcheck) and /api/health (external, through Caddy):

Terminal window
# Internal (from within Docker network)
curl http://api-prod:8000/health
# External (through Caddy)
curl https://space.your-domain.com/api/health
Terminal window
docker compose exec db psql -U astrolock -d astrolock
Terminal window
docker compose --profile prod down -v
docker compose --profile prod up --build -d
docker compose exec api-prod alembic upgrade head
docker compose exec api-prod python -m astrolock_api.seed
docker compose exec api-prod python -m astrolock_api.backfill_search_text

The key variables in .env:

VariableDefaultPurpose
CRAFT_MODEdevNot used by Compose directly; documentation convention
CRAFT_DOMAINDomain for Caddy TLS and routing
POSTGRES_PASSWORDPostgreSQL password (required)
GPU_API_KEYAPI key for the embedding endpoint
GPU_BASE_URLhttps://astrolock.gpu.supported.systems/v1OpenAI-compatible embedding endpoint
GPU_EMBED_MODELmxbai-embed-largeEmbedding model name
OBSERVER_LAT / OBSERVER_LON / OBSERVER_ALT36.0 / -86.0 / 200Default observer location
ROTOR_DEFAULT_HOST / ROTOR_DEFAULT_PORT127.0.0.1 / 4533Default rotctld connection
ROTOR_MIN_ELEVATION5.0Minimum elevation for rotor commands (degrees)
SPACETRACK_USER / SPACETRACK_PASSOptional Space-Track credentials
VITE_CESIUM_TOKENCesiumJS ion access token for the 3D globe
VITE_API_URL / VITE_WS_URLAPI and WebSocket URLs for the frontend