Stack overview
NubleStation runs as a single Docker Compose stack in infra/docker-compose.yml. Every service is a separate container, communicating over the internal nuble Docker bridge network.
# High-level structure
services:
caddy # reverse proxy + static files — ports 80, 443 published to host
coredns # LAN DNS — port 53 published to host
api # Gateway — API entry point, internal only, Caddy forwards to it
blaze # Database service — internal only
vault # File storage service — internal only
orbit # Deploy service — internal only
console # Next.js admin dashboard — internal only, Caddy forwards to it
identity # Auth service — sessions, shared-cookie SSO
postgres # PostgreSQL 16 — internal only
networks:
nuble: # internal bridge — services communicate here
volumes:
postgres-data: # Postgres data directory (persistent)
nuble-apps: # deployed frontend files (/var/nuble/apps/)
nuble-storage: # Vault file storage (/var/nuble/storage/)
caddy-data: # Caddy TLS certificates and state
Published ports
Only two ports are published to the host network interface (and therefore to the LAN):
| Port | Service | Purpose |
|---|---|---|
53/udp, 53/tcp | CoreDNS | LAN DNS resolution |
80/tcp | Caddy | HTTP (redirects to HTTPS) |
443/tcp | Caddy | HTTPS (with internal CA cert) |
Every other service listens only on the internal nuble network. No other port is reachable from the LAN.
Service dependencies
caddy
└── depends_on: gateway, console
api (gateway)
└── depends_on: postgres, blaze, vault
blaze, vault, orbit
└── depends_on: postgres
postgres
└── (no dependencies — starts first)
redis
└── (no dependencies — starts first)
coredns
└── (no dependencies — starts first)
Health checks
Every service declares a health check. Docker marks it healthy only when the check passes. Compose’s condition: service_healthy ensures dependent services wait:
gateway:
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# Check all services
docker compose -f infra/docker-compose.yml ps
# Tail logs for a specific service
docker compose -f infra/docker-compose.yml logs db --follow
# Restart a single service
docker compose -f infra/docker-compose.yml restart gateway
Volumes
| Volume | Mounted at | Contents |
|---|---|---|
postgres-data | /var/lib/postgresql/data | Postgres data files |
nuble-files | /var/nuble/ | Deployed frontend bundles + uploaded files |
caddy-data | /data | Caddy TLS state (certificates) |
postgres-data is persistent — it survives container restarts and upgrades. Removing it with down -v erases all data.
Environment variables
All secrets are in infra/.env, generated by scripts/install.sh. The Compose file reads them with env_file: .env.
Key variables:
| Variable | Description |
|---|---|
ORG_NAME | Organization name — used as subdomain root |
HOST_IP | LAN IP of the host machine |
POSTGRES_PASSWORD | Generated at install time |
INTERNAL_HMAC_SECRET | Shared HMAC secret across gateway + all services |
REDIS_PASSWORD | Generated at install time |
Auto-restart policy
All services use restart: unless-stopped. If a container crashes, Docker restarts it automatically. If the host machine reboots, Docker starts the stack if Docker is configured to start on boot (systemctl enable docker).
Upgrading
To upgrade NubleStation services to a newer version:
# Pull new images
docker compose -f infra/docker-compose.yml pull
# Recreate containers with new images (zero-downtime for stateless services)
docker compose -f infra/docker-compose.yml up -d --remove-orphans
Platform migrations run automatically at DB service boot — the service applies any pending schema changes before accepting traffic.