Skip to main content

Overview

Open Wearables publishes two production images to Docker Hub:
ImageServicePort
themomentum/open-wearables-backendFastAPI API + Celery worker/beat/flower8000
themomentum/open-wearables-frontendReact frontend (TanStack Start, served by Nitro)3000
The single backend image runs the API, worker, beat, and flower — the command is supplied by your orchestrator (scripts/start/app.sh, worker.sh, beat.sh, flower.sh), exactly as in docker-compose.yml.

Configuring the frontend API URL at runtime

What changed: the frontend used to bake VITE_API_URL into the JavaScript bundle at build time, which meant a prebuilt image could only ever talk to one backend. The API URL is now resolved at runtime, so the same published image works against any backend without rebuilding.
Set VITE_API_URL as an environment variable on the frontend container. The Nitro server reads it at startup and injects it into the served HTML before the app loads:
docker run -p 3000:3000 \
  -e VITE_API_URL=https://api.client1.com \
  themomentum/open-wearables-frontend:latest
The same image with a different value points at a different backend — no rebuild:
docker run -p 3000:3000 \
  -e VITE_API_URL=https://api.client2.com \
  themomentum/open-wearables-frontend:latest
If VITE_API_URL is not set, it falls back to http://localhost:8000.
VITE_API_URL is a single variable used for both build-time (Vite inlines import.meta.env) and runtime configuration. When the same variable is set both in an .env file and via the container’s environment:, the container environment wins — so runtime always overrides any baked-in value.

How it works

Because the frontend is server-rendered (TanStack Start on Nitro), the value travels from the container env to the browser like this:
  1. The Nitro server reads process.env.VITE_API_URL at request time.
  2. It injects window.__APP_CONFIG__ = { apiUrl: "..." } into the HTML <head> before the app hydrates.
  3. The API client reads that value. (Resolution order: injected runtime value → VITE_API_URL baked at build → http://localhost:8000.)
The logic lives in frontend/src/lib/api/runtime-config.ts.

Example: deploying one client

For a client served at client1.com with its API at api.client1.com:
services:
  frontend:
    image: themomentum/open-wearables-frontend:latest
    environment:
      - VITE_API_URL=https://api.client1.com
    ports:
      - "3000:3000"

  backend:
    image: themomentum/open-wearables-backend:latest
    command: scripts/start/app.sh
    env_file:
      - ./config/.env
    ports:
      - "8000:8000"
The backend reads all of its configuration from the environment at runtime (config/.env), so its image needs no per-client rebuild either.

Publishing the images

Images are built and pushed manually via the Publish Docker images GitHub Actions workflow (.github/workflows/publish-images.yml):
  1. Go to Actions → Publish Docker images → Run workflow.
  2. Both images are built and pushed, tagged with the commit SHA, plus latest when run from main, plus any optional tag you provide.
The workflow requires two repository secrets: DOCKERHUB_USERNAME and DOCKERHUB_TOKEN (a Docker Hub access token with Read & Write scope). No API URL is baked into the frontend image — it is always configured at runtime as described above.