Docker & Setup
Deploy GroundWave using Docker Compose on any 64-bit hardware.
10 min readContainers
GroundWave runs as four Docker containers orchestrated by Docker Compose. Only the Nginx
reverse proxy exposes ports to the host network; all other containers communicate over a
private internal Docker network named groundwave-net.
| Container | Technology | Port | Visibility |
|---|---|---|---|
groundwave-web |
Nginx | 80 / 443 | Exposed to host & clients |
groundwave-app |
Node.js / Express / Socket.IO | 3000 | Internal only |
groundwave-tiles |
TileServer GL | 8080 | Internal only |
groundwave-db |
PostgreSQL + PostGIS | 5432 | Internal only |
An optional fifth container is available for TAK interoperability. It is activated separately via a Docker Compose profile and does not start with the main stack by default.
| Container | Technology | Port | Activation |
|---|---|---|---|
groundwave-bridge |
Node.js CoT Bridge | 4242 (TCP) | --profile bridge |
All containers are configured with restart: unless-stopped, providing
automatic recovery after host reboots or container crashes without requiring manual
intervention.
Development Setup
The repository ships with a development Docker Compose override that builds images locally and enables hot reload for rapid iteration. No pre-built images or registry credentials are required.
-
Clone the repository
git clone <repository-url> cd groundwave -
Copy the environment template
Editcp config/.env.example config/.envconfig/.envwith your desired settings before starting. -
Start the stack
Docker Compose automatically mergesdocker compose up -ddocker-compose.ymlwith the dev override file, building local images for all services. -
Access the app
Open
https://localhostin your browser. Accept the self-signed certificate on first load. The app server auto-restarts on file changes toserver/src/. Client changes are served via Vite HMR.
Use docker compose logs -f groundwave-app to stream structured Pino
log output from the application container in real time during development.
Production Setup
The production docker-compose.yml references local image tags produced by
the release build script. The intended deployment workflow is:
-
Build a release archive on your development machine:
This script builds multi-stage Docker images for both./scripts/build-release.shlinux/amd64andlinux/arm64, exports them as.tararchives, and bundles them with the Compose file, Nginx config, env template, andsetup.shinto a self-contained distributable tarball. - Transfer the tarball to the target machine via USB drive, SCP, or any available transport — no internet connection is required on the target.
-
Extract and run the setup script:
tar xzf groundwave-release-*.tar.gz cd groundwave-release ./setup.sh
The release build eliminates Node.js as a host dependency by using a multi-stage client Dockerfile: the first stage compiles the React application inside a Node.js builder image, and the second stage copies only the compiled static assets into the final Nginx image. The target machine needs only Docker.
Setup Script
setup.sh is the entry point for target-machine deployment. It handles
everything from image loading to TLS certificate generation:
- Architecture detection — identifies
x86_64oraarch64and loads the appropriate image tarball. - Docker image loading — calls
docker loadon each bundled.tarfile. No registry or internet access required. - Environment configuration — prompts for required settings if
config/.envdoes not exist, then writes the file. - TLS certificate generation — creates a private Certificate Authority and a server certificate signed by that CA. Nginx is configured to use these certificates automatically.
- Container startup — runs
docker compose up -dto start all services.
Registry Mode
When internet connectivity is available on the target machine, you can skip the image tarball entirely and pull directly from the GitHub Container Registry:
./setup.sh --from-registry
This mode uses docker-compose.registry.yml as an overlay, which references
GHCR image tags instead of local image names. Architecture selection is handled
automatically by Docker's manifest list — the correct image variant is pulled based on
the host's CPU architecture.
Environment Variables
All configuration is provided through environment variables defined in
config/.env. The following table lists the most important variables:
| Variable | Default | Description |
|---|---|---|
AUTH_REQUIRED |
false |
Set to true to enable password-based authentication. When disabled, any user can connect with a callsign and no credentials are checked. |
JWT_SECRET |
— | Required when AUTH_REQUIRED=true. A long random string used to sign JWT tokens. Generate with openssl rand -hex 32. |
JWT_EXPIRY |
24h |
JWT token lifetime. Accepts values like 1h, 8h, 7d. Clients detect expiry and redirect to the login page. |
LOG_LEVEL |
info |
Pino log level. Valid values: error, warn, info, debug. Use debug to trace Socket.IO events and database queries. |
FEATURES_ENABLED |
all enabled | Comma-separated list of active subsystems. Valid tokens: chat, markers, files, overlays, federation. Omit a token to disable that feature at the server and client level. |
SERVER_NAME |
GroundWave |
Human-readable server name used in federation peer identification. Federated users appear as ServerName::Callsign on remote servers. |
HEALTH_API_KEY |
— | Optional API key for the detailed health endpoint. When set, requests to GET /api/health/detailed must include the key in the X-API-Key header. |
POSITION_RETENTION_HOURS |
24 |
How long position history is retained before the scheduled purge job removes old records. Tune this to manage database size on constrained hardware. |
Never commit config/.env to version control. It contains
JWT_SECRET and any federation API keys. The file is listed in
.gitignore by default.
Multi-Architecture Builds
GroundWave ships pre-built images for two CPU architectures, covering the full range of supported deployment targets:
| Architecture | Target Hardware |
|---|---|
linux/amd64 |
Intel NUC, standard x86 servers, most cloud VMs |
linux/arm64 |
Raspberry Pi 4 / 5, Apple Silicon (via Docker Desktop), ARM servers |
The GitHub Actions CI workflow builds both variants on every tagged release using Docker Buildx with QEMU emulation for cross-compilation. The multi-stage Dockerfile for the client ensures Node.js is only present in the builder stage — the final Nginx image contains only compiled static assets, keeping the production image lean.
Registry Deployment
Pre-built images are published to the GitHub Container Registry (GHCR) as part of the CI release workflow. This provides an alternative to the bundled-tarball deployment method when WAN connectivity is available on the target machine.
The docker-compose.registry.yml override file replaces local image
references with GHCR image tags. Use it with the standard Compose override mechanism:
docker compose -f docker-compose.yml -f docker-compose.registry.yml up -d
Or use the setup script shorthand, which applies the override automatically:
./setup.sh --from-registry
Docker pulls the correct image variant automatically based on the host architecture — no manual architecture selection is needed.
Health Checks
GroundWave exposes two health endpoints for monitoring and orchestration:
No authentication required. Suitable for Docker health check directives and load balancer probes.
Optional X-API-Key header if HEALTH_API_KEY is configured. Returns CPU load, RAM usage, disk usage, container status, connected client count, and server version.
Docker Compose restart policies (restart: unless-stopped) provide
automatic container recovery. If the application container crashes, Docker restarts it
within seconds without requiring administrator intervention. Combined with the basic
health check, this creates a resilient self-healing deployment suitable for unattended
field operation.
The detailed health endpoint is also surfaced in the Admin Dashboard under the System Status tab, providing a real-time view of resource consumption without needing direct server access.