Docker & Setup

Deploy GroundWave using Docker Compose on any 64-bit hardware.

10 min read

Containers

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.

  1. Clone the repository
    git clone <repository-url>
    cd groundwave
  2. Copy the environment template
    cp config/.env.example config/.env
    Edit config/.env with your desired settings before starting.
  3. Start the stack
    docker compose up -d
    Docker Compose automatically merges docker-compose.yml with the dev override file, building local images for all services.
  4. Access the app

    Open https://localhost in your browser. Accept the self-signed certificate on first load. The app server auto-restarts on file changes to server/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:

  1. Build a release archive on your development machine:
    ./scripts/build-release.sh
    This script builds multi-stage Docker images for both linux/amd64 and linux/arm64, exports them as .tar archives, and bundles them with the Compose file, Nginx config, env template, and setup.sh into a self-contained distributable tarball.
  2. Transfer the tarball to the target machine via USB drive, SCP, or any available transport — no internet connection is required on the target.
  3. 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_64 or aarch64 and loads the appropriate image tarball.
  • Docker image loading — calls docker load on each bundled .tar file. No registry or internet access required.
  • Environment configuration — prompts for required settings if config/.env does 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 -d to 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:

GET /api/health Basic liveness check — returns HTTP 200 when the app server is running

No authentication required. Suitable for Docker health check directives and load balancer probes.

GET /api/health/detailed Full system telemetry

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.