6.6 KiB
DigitalOcean Deployment Spec
PodMan deploys as three App Platform components from the same monorepo:
web: static Vite/React frontendapi: health-checked HTTP backend on port8787podman-agent: background LiveKit/Gemini worker with no HTTP health check
This split is intentional. The LiveKit agent subscribes to rooms and samples screen-share frames, so it must run as a worker rather than as a web service.
Canonical Spec
Use infra/app.yaml:
doctl apps create --spec infra/app.yaml
The mirror at infra/.do/app.yaml is kept identical for DO UI/import workflows.
Components
Static Site: web
- Source: monorepo root
- Build:
corepack enable && pnpm install --frozen-lockfile && pnpm --filter @podman/shared build && pnpm --filter @podman/frontend build - Output:
frontend/dist - Routes:
/ - Build-time env:
VITE_BACKEND_URLVITE_LIVEKIT_URL- In the App Platform spec,
VITE_BACKEND_URL=${APP_URL}keeps frontend API calls on the same deployed origin. If it is omitted, the production frontend also falls back to same-origin.
HTTP Service: api
- Source: monorepo root
- Dockerfile:
infra/Dockerfile - Runtime selector:
PODMAN_PROCESS=server - Port:
8787 - Health check:
/health - Routes:
/apiwithpreserve_path_prefix: true/health
Worker: podman-agent
- Source: monorepo root
- Dockerfile:
infra/Dockerfile - Runtime selector:
PODMAN_PROCESS=agent - No HTTP route and no HTTP health check
- Default room:
POD_ROOM=demo-pod
Required Runtime Environment
LIVEKIT_URL=wss://your-livekit-server.livekit.cloud
LIVEKIT_API_KEY=...
LIVEKIT_API_SECRET=...
GEMINI_API_KEY=...
GEMINI_VISION_MODEL=gemini-2.0-flash
GEMINI_LIVE_MODEL=gemini-3.1-flash-tts-preview
GEMINI_TTS_VOICE=Charon
GITHUB_TOKEN=...
GITHUB_REPO=karti-ai/podman
MONGODB_URI=mongodb+srv://...
VOYAGE_API_KEY=...
VOYAGE_EMBEDDING_MODEL=voyage-4-lite
PORT=8787
POD_ROOM=demo-pod
VOYAGE_API_KEY is optional for local/demo fallback. Without it, Mongo exact
signature recall still works; Atlas Vector Search recall is skipped.
Container Checks
Build once:
docker build -f infra/Dockerfile -t podman-backend .
Or use the repository script and verifier:
pnpm build:container
pnpm verify:containers
verify:containers uses Docker by default to match build:container. To verify
against a Podman image store instead, run
VERIFY_CONTAINER_RUNTIME=podman pnpm verify:containers.
The image entrypoint runs node backend/dist/server.js when
PODMAN_PROCESS=server, and node backend/dist/agent.js when
PODMAN_PROCESS=agent. Do not run the combined Hermes supervisor inside App
Platform; DO already supervises the service and worker separately.
Run the API:
docker run --env-file backend/.env -e PODMAN_PROCESS=server -p 8787:8787 podman-backend
Run the worker:
docker run --env-file backend/.env -e PODMAN_PROCESS=agent podman-backend
Deploy Checklist
VITE_BACKEND_URLis${APP_URL}or points to the deployed API origin.FRONTEND_URLis set forpnpm deploy:doctor:strictif the SPA is on a different origin than the API.LIVEKIT_URLpoints to the LiveKit Cloud project.- LiveKit API key/secret are set for both
apiandpodman-agent. - Gemini API key is set for both backend components.
- MongoDB Atlas allows DigitalOcean outbound access.
GET /returns the built frontend HTML and JavaScript bundle.GET /healthreturns{ "ok": true }.GET /api/podsreturns pod data.- Worker logs show
podman-hermes joined room demo-pod. pnpm deploy:doctor:strictpasses with production env loaded.
Run a non-failing readiness report any time:
pnpm deploy:doctor
Use the strict gate before calling a deployment production-ready:
pnpm deploy:doctor:strict
Local Fallback
pnpm install --frozen-lockfile
pnpm build
pnpm --filter @podman/backend start:server
pnpm --filter @podman/backend start:agent
pnpm --filter @podman/frontend dev
For this droplet deployment, Caddy serves frontend/dist from
/var/www/podman and proxies /api/* to localhost:8787.
The systemd fallback units live in infra/systemd/ and load
/root/podman/backend/.env on the current droplet:
sudo cp infra/systemd/podman-platform-*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now podman-platform-api podman-platform-agent
The droplet production fallback uses systemd units from infra/systemd/:
sudo install -m 0644 infra/systemd/podman-platform-api.service /etc/systemd/system/
sudo install -m 0644 infra/systemd/podman-platform-agent.service /etc/systemd/system/
sudo install -m 0644 infra/systemd/podman-hermes-*.service infra/systemd/podman-hermes-*.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now podman-platform-api podman-platform-agent podman-hermes-watchdog.timer podman-hermes-sync-deploy.timer
Expected runtime proof:
systemctl is-active podman-platform-api podman-platform-agent
systemctl is-active podman-hermes-watchdog.timer
systemctl is-active podman-hermes-sync-deploy.timer
curl http://127.0.0.1:8787/health
journalctl -u podman-platform-agent -n 20 --no-pager
journalctl -u podman-hermes-watchdog -n 40 --no-pager
Hermes-managed operations layer
The app processes are still supervised by systemd, but Hermes now owns the operations loop around them:
pnpm hermes:watchdogchecks systemd services, public routes,/health,/api/pods, andpnpm deploy:doctor.podman-hermes-watchdog.timerruns that watchdog every five minutes.podman-hermes-sync-deploy.timerpollsorigin/mainevery two minutes. If the tree is clean and the remote moved, it fast-forwards, installs, builds, publishesfrontend/distto/var/www/podman, restarts the API/agent/Caddy, and runs the strict watchdog.- Failed URL checks trigger restarts of the PodMan API, PodMan agent, and Caddy.
- Failed service checks restart only the unhealthy service.
- Caddy is validated and reloaded after public route failures.
- Reports are written to
/var/log/podman/hermes-watchdog-latest.json. - Set
PODMAN_ALERT_WEBHOOK_URLto send failed reports to Discord, Slack, or a generic webhook receiver. pnpm hermes:installinstalls the timer units and a local pre-push hook that gates major pushes with typecheck, lint, and a non-remediating watchdog check.
The strict gate for production readiness is:
pnpm hermes:watchdog:strict
Manual deploy-sync run:
pnpm hermes:sync-deploy