This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Server Installation

Prefer not to run a server? Nebula Commander Cloud hosts it for you.

You can run Nebula Commander in several ways:

MethodBest for
Docker (recommended)Quick start and production: pre-built images, docker compose
NixOSNixOS hosts: services.nebula-commander (server) and services.ncclient (device client) modules
Reverse ProxyNginx, Traefik, or Caddy in front with TLS and HSTS

Choose one and follow the linked page. For local development (venv, uvicorn, frontend dev server), see Development: Setup. After installation, see Server Configuration to set the database, JWT, and optional OIDC.

1 - Docker

Run Nebula Commander with Docker using pre-built images or a local build. You can download all required files without cloning the repository. For HTTPS and HSTS at the edge (Nginx, Traefik, or Caddy in front of the frontend container), see Reverse Proxy.

Downloading files

You need: docker-compose.yml, docker-compose-keycloak.yml, .env.example, and the env.d.example/ directory (backend and Keycloak config). Either use the commands below or the provided script.

Using curl

Run these in an empty directory (or where you want the Docker setup):

BASE_URL="https://raw.githubusercontent.com/NixRTR/nebula-commander/main/docker"

curl -sSL -o docker-compose.yml "${BASE_URL}/docker-compose.yml"
curl -sSL -o docker-compose-keycloak.yml "${BASE_URL}/docker-compose-keycloak.yml"
curl -sSL -o .env.example "${BASE_URL}/.env.example"

mkdir -p env.d.example/keycloak
curl -sSL -o env.d.example/backend "${BASE_URL}/env.d.example/backend"
curl -sSL -o env.d.example/keycloak/keycloak "${BASE_URL}/env.d.example/keycloak/keycloak"
curl -sSL -o env.d.example/keycloak/postgresql "${BASE_URL}/env.d.example/keycloak/postgresql"

Then create the Docker network (required by the compose files):

docker network create nebula-commander

Copy the example env into place and edit as needed:

cp .env.example .env
cp -r env.d.example env.d
# Edit env.d/backend (JWT secret, OIDC, etc.)

Using the download script

The repository provides a script that downloads the same files and checks for prerequisites. It does not install anything; if Docker or Docker Compose is missing, it prints what you need and exits.

curl -sSL https://raw.githubusercontent.com/NixRTR/nebula-commander/main/docker/download.sh | bash

Then run the steps the script prints: copy .env.example to .env, copy env.d.example to env.d, edit env.d/backend, create the network, and start with docker compose up -d.


Example file contents

Below are the default file contents for reference. After downloading, copy to .env and env.d/ and customize.

docker-compose.yml

name: nebulacdr

include:
  - path: ./docker-compose-keycloak.yml

services:

  backend:
    build:
      context: ..
      dockerfile: docker/backend/Dockerfile
    image: ghcr.io/nixrtr/nebula-commander-backend:latest
    container_name: nebula-commander-backend
    restart: unless-stopped
    ports:
      - "${BACKEND_PORT:-8081}:8081"
    volumes:
      # Persistent data storage
      - nebula-commander-data:/var/lib/nebula-commander
      # Optional: mount JWT secret file
      - ${JWT_SECRET_FILE:-/dev/null}:/run/secrets/jwt-secret:ro
    env_file:
      # Backend configuration (database, JWT, OIDC, CORS, debug)
      - env.d/backend
    environment:
      - NEBULA_COMMANDER_SERVER_HOST=0.0.0.0
      - NEBULA_COMMANDER_SERVER_PORT=8081
    healthcheck:
      test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8081/api/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    networks:
      - nebula-commander

  frontend:
    build:
      context: ..
      dockerfile: docker/frontend/Dockerfile
    image: ghcr.io/nixrtr/nebula-commander-frontend:latest
    container_name: nebula-commander-frontend
    restart: unless-stopped
    ports:
      - "${FRONTEND_PORT:-80}:80"
    depends_on:
      backend:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s
    networks:
      - nebula-commander

networks:
  nebula-commander:
    external: true

volumes:
  nebula-commander-data:
    driver: local

docker-compose-keycloak.yml

# Keycloak OIDC Authentication Stack
# To use: docker compose -f docker-compose.yml -f docker-compose-keycloak.yml up -d

services:
  keycloak_db:
    image: postgres:16-alpine
    container_name: keycloak-db
    restart: unless-stopped
    env_file:
      - env.d/keycloak/postgresql
    volumes:
      - keycloak-db-data:/var/lib/postgresql/data
    networks:
      - nebula-commander
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-keycloak}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  keycloak:
    build:
      context: ..
      dockerfile: docker/keycloak/Dockerfile
    image: ghcr.io/nixrtr/nebula-commander-keycloak:latest
    container_name: keycloak
    restart: unless-stopped
    env_file:
      - env.d/keycloak/keycloak
      - env.d/backend
    ports:
      - "${KEYCLOAK_PORT:-8080}:8080"
      - "${KEYCLOAK_ADMIN_PORT:-9000}:9000"
    depends_on:
      keycloak_db:
        condition: service_healthy
    networks:
      - nebula-commander
    healthcheck:
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080 && echo -e 'GET /health/ready HTTP/1.1\\r\\nhost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q '200 OK'"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

volumes:
  keycloak-db-data:
    driver: local

networks:
  nebula-commander:
    external: true

.env.example

# Nebula Commander Docker Infrastructure Configuration
# Copy to .env and customize. Backend settings go in env.d/backend.

# Port for the frontend (Nginx)
FRONTEND_PORT=80

# Port for the backend (FastAPI)
# BACKEND_PORT=8081

# Port for Keycloak (when using docker-compose-keycloak.yml)
# KEYCLOAK_PORT=8080

# Path to JWT secret file on host (optional)
# JWT_SECRET_FILE=/path/to/jwt-secret.txt

env.d.example/backend

Full example. See Configuration: Environment for all options.

# =============================================================================
# Nebula Commander Backend Configuration
# =============================================================================
# This file contains all backend-specific settings including database, JWT,
# OIDC, CORS, and debug mode. All variables use the NEBULA_COMMANDER_ prefix.

# =============================================================================
# Database Configuration
# =============================================================================

# SQLite database URL: use four slashes for absolute path (so DB is at /var/lib/..., not CWD/var/lib/...)
NEBULA_COMMANDER_DATABASE_URL=sqlite+aiosqlite:////var/lib/nebula-commander/db.sqlite

# Certificate storage path (inside container)
NEBULA_COMMANDER_CERT_STORE_PATH=/var/lib/nebula-commander/certs

# =============================================================================
# JWT Authentication
# =============================================================================

# JWT secret key for signing tokens
# WARNING: Generate a secure random key for production! Use: openssl rand -base64 32
# DO NOT use this example value in production!
NEBULA_COMMANDER_JWT_SECRET_KEY=CHANGE_ME_GENERATE_RANDOM_32_CHARS_MIN

# Alternative: Use a secret file (more secure)
# NEBULA_COMMANDER_JWT_SECRET_FILE=/run/secrets/jwt-secret

# JWT algorithm (HS256, HS384, HS512)
NEBULA_COMMANDER_JWT_ALGORITHM=HS256

# JWT token expiration in minutes (1440 = 24 hours)
NEBULA_COMMANDER_JWT_EXPIRATION_MINUTES=1440

# =============================================================================
# Encryption at rest (required)
# =============================================================================
# Fernet key for encrypting sensitive DB columns and cert store files.
# Generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# Required at startup - the backend refuses to boot without one.
NEBULA_COMMANDER_ENCRYPTION_KEY=your-fernet-key-here

# Alternative: key from file
# NEBULA_COMMANDER_ENCRYPTION_KEY_FILE=/run/secrets/encryption-key

# =============================================================================
# Public URL (required for OIDC and redirects)
# =============================================================================
# Base URL where users reach the app (browser). All auth redirects and callbacks
# use this. Supports FQDN or host:port. Backend and frontend may be on different hosts.
# Examples: https://nebula.example.com  |  http://192.168.1.1:9091
# If set, OIDC redirect URI is derived as PUBLIC_URL + /api/auth/callback.
NEBULA_COMMANDER_PUBLIC_URL=https://nebula.example.com

# =============================================================================
# Standalone Admin Bootstrap (only relevant if you skip OIDC entirely)
# =============================================================================
# When no OIDC provider is configured below, /api/auth/dev-token is disabled by
# default. If you're running standalone with no IdP and need the UI to be able
# to self-issue an initial admin session, opt in explicitly here.
# WARNING: This grants unauthenticated system-admin access to anyone who can
# reach the backend. Only enable for a genuinely no-IdP deployment.
# NEBULA_COMMANDER_STANDALONE_ADMIN_BOOTSTRAP=true

# =============================================================================
# OIDC Configuration (Optional)
# =============================================================================
# Uncomment and configure to use OIDC authentication with Keycloak or other providers.
# All user-facing auth (login redirect, callback, logout) goes through the public URL above.

# OIDC Issuer URL (internal - backend→Keycloak, same Docker network only)
# Use CONTAINER port (8080). If Keycloak host port is 8082, still use 8080 here.
NEBULA_COMMANDER_OIDC_ISSUER_URL=http://keycloak:8080/realms/nebula-commander

# OIDC Public Issuer URL (external - for browser: logout, discovery)
# Keycloak as seen by the user's browser: FQDN or host:port.
# Examples: https://auth.example.com/realms/nebula-commander  |  http://host:8082/realms/nebula-commander
NEBULA_COMMANDER_OIDC_PUBLIC_ISSUER_URL=https://auth.example.com/realms/nebula-commander

# OIDC Client ID (configured in Keycloak)
NEBULA_COMMANDER_OIDC_CLIENT_ID=nebula-commander

# OIDC Client Secret (from Keycloak client credentials)
# WARNING: Replace with your actual Keycloak client secret! Do not use this placeholder.
NEBULA_COMMANDER_OIDC_CLIENT_SECRET=YOUR_KEYCLOAK_CLIENT_SECRET_HERE

# Alternative: Use a secret file (more secure)
# NEBULA_COMMANDER_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc-secret

# Redirect URI: derived from NEBULA_COMMANDER_PUBLIC_URL when unset (PUBLIC_URL + /api/auth/callback).
# Override only if you need a different callback URL.
# NEBULA_COMMANDER_OIDC_REDIRECT_URI=

# OIDC Scopes (space-separated)
NEBULA_COMMANDER_OIDC_SCOPES=openid profile email

# =============================================================================
# CORS Configuration
# =============================================================================

# Allowed CORS origins (comma-separated). Include your public app URL (FQDN or host:port).
# SECURITY WARNING: Using '*' with credentials is insecure!
NEBULA_COMMANDER_CORS_ORIGINS=https://nebula.example.com

# =============================================================================
# Session Security
# =============================================================================

# Set session cookie to HTTPS-only (recommended for production)
NEBULA_COMMANDER_SESSION_HTTPS_ONLY=false

# =============================================================================
# Redirect Security
# =============================================================================

# Allowed hosts for OAuth/OIDC redirects (comma-separated)
# When empty and NEBULA_COMMANDER_PUBLIC_URL is set, derived from PUBLIC_URL (recommended).
NEBULA_COMMANDER_ALLOWED_REDIRECT_HOSTS=

# =============================================================================
# Email / SMTP Configuration (Optional)
# =============================================================================

NEBULA_COMMANDER_SMTP_ENABLED=false
NEBULA_COMMANDER_SMTP_HOST=smtp.gmail.com
NEBULA_COMMANDER_SMTP_PORT=587
NEBULA_COMMANDER_SMTP_USE_TLS=true
# For Gmail, use an App Password, not your regular password.
NEBULA_COMMANDER_SMTP_USERNAME=your-email@gmail.com
NEBULA_COMMANDER_SMTP_PASSWORD=your-app-password
# Alternative: NEBULA_COMMANDER_SMTP_PASSWORD_FILE=/run/secrets/smtp-password
NEBULA_COMMANDER_SMTP_FROM_EMAIL=noreply@example.com
NEBULA_COMMANDER_SMTP_FROM_NAME=Nebula Commander

# =============================================================================
# Analytics (Optional)
# =============================================================================
# When set, the frontend fetches /api/public-config and injects the corresponding
# scripts into the page. No auth required; config is public.

# NEBULA_COMMANDER_PLAUSIBLE_DOMAIN=example.com
# NEBULA_COMMANDER_PLAUSIBLE_SCRIPT_SRC=https://plausible.example.com/js/script.file-downloads.hash.outbound-links.js
# NEBULA_COMMANDER_GA_MEASUREMENT_ID=
# NEBULA_COMMANDER_ANALYTICS_CUSTOM_SCRIPTS=[{"src":"https://example.com/script.js","defer":true}]

# =============================================================================
# Debug Mode
# =============================================================================

# Enable debug mode (enables /dev-token endpoint). Never enable in production!
NEBULA_COMMANDER_DEBUG=false

env.d.example/keycloak/keycloak

# Keycloak Configuration
KC_DB=postgres
KC_DB_URL_HOST=keycloak_db
KC_DB_URL_PORT=5432
KC_DB_URL_DATABASE=keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=keycloak_db_password

KC_BOOTSTRAP_ADMIN_USERNAME=admin
KC_BOOTSTRAP_ADMIN_PASSWORD=admin

KC_HOSTNAME=localhost
KC_HOSTNAME_STRICT=false
KC_HTTP_ENABLED=true
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
KC_LOG_LEVEL=info

env.d.example/keycloak/postgresql

# PostgreSQL Configuration for Keycloak
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=keycloak_db_password

Quick start (pre-built images)

After you have the files and env.d/backend configured:

docker network create nebula-commander   # if not already created
docker compose pull
docker compose up -d
docker compose logs -f

The application is available at http://localhost (or the port set in .env).

Building locally

If you prefer to build images instead of pulling:

docker compose build
docker compose up -d

See Development: Manual builds for build-args and multi-arch builds.

Images

ImageRegistryBasePortPlatforms
Backendghcr.io/nixrtr/nebula-commander-backend:latestPython 3.13-slim8081linux/amd64, linux/arm64
Frontendghcr.io/nixrtr/nebula-commander-frontend:latestnginx:alpine80linux/amd64, linux/arm64
Keycloakghcr.io/nixrtr/nebula-commander-keycloak:latestKeycloak8080linux/amd64, linux/arm64

Architecture

  • Frontend (Nginx) – Serves the React SPA and proxies /api/* to the backend. Port 80.
  • Backend (FastAPI) – REST API, certificate management, SQLite. Port 8081.
  • Persistent volume – SQLite database and Nebula certificates under /var/lib/nebula-commander.
Frontend (Nginx) :80  -->  Backend (FastAPI) :8081  -->  Volume (db + certs)

Configuration

Use two places:

  1. Infrastructure – .env: ports, optional JWT secret file path.
  2. Backend – env.d/backend: all NEBULA_COMMANDER_* variables (database, JWT, OIDC, CORS, SMTP, debug).

See Configuration for full option lists.

With Keycloak (OIDC)

The default docker-compose.yml includes the Keycloak stack via include: path: ./docker-compose-keycloak.yml, so a single command starts backend, frontend, and Keycloak:

docker compose up -d

Configure OIDC in env.d/backend and optionally use the zero-touch Keycloak setup. Details: Configuration: OIDC.

Without Keycloak

Comment out (or delete) the include: block at the top of docker-compose.yml:

# include:
#   - path: ./docker-compose-keycloak.yml

Then docker compose up -d starts only the backend and frontend. With no OIDC provider configured, the backend exposes /api/auth/dev-token (suitable for development only). To use a different OIDC provider instead of Keycloak, see Configuration: OIDC § External OIDC provider.

2 - NixOS

Run Nebula Commander as a NixOS service by adding the module and enabling the service. You can add the module from a local path (clone) or, when available, from a flake.

Adding the module (path-based, no flake)

Use this when you have the nebula-commander repository on disk (for example under /etc/nixos or a path you manage).

1. Get the repository

Clone or copy the nebula-commander repo so that the path contains both nix/ and backend/:

git clone https://github.com/NixRTR/nebula-commander.git /etc/nixos/nebula-commander
# Or use a path of your choice; the module expects ../backend relative to nix/module.nix

2. Import the module in your NixOS configuration

In configuration.nix (or a NixOS module you include), add the import and enable the service:

{
  imports = [
    /etc/nixos/nebula-commander/nix/module.nix
  ];

  services.nebula-commander.enable = true;
}

If you use a different path, use that path in imports, for example ./nebula-commander/nix/module.nix if the repo is in the same directory as your configuration.nix.

3. Optional: set options

Override any of the options (see the table below). The default package builds the backend from the same repo: it copies backend/ from the path relative to nix/module.nix (../backend), so your clone must have that layout.

services.nebula-commander = {
  enable = true;
  backendPort = 8081;
  databasePath = "/var/lib/nebula-commander/db.sqlite";
  certStorePath = "/var/lib/nebula-commander/certs";
  jwtSecretFile = null;   # or e.g. /run/secrets/nebula-commander-jwt
  encryptionKeyFile = null;   # or e.g. /run/secrets/nebula-commander-encryption-key
  debug = false;

  # Production/OIDC options (all optional):
  publicUrl = "https://nebula.example.com";
  corsOrigins = "https://nebula.example.com";
  sessionHttpsOnly = true;
  oidc = {
    issuerUrl = "https://keycloak.example.com/realms/nebula-commander";
    clientId = "nebula-commander";
    clientSecretFile = "/run/secrets/nebula-commander-oidc-secret";
  };
};

Then rebuild: nixos-rebuild switch (or your usual method).


Adding via a flake

The nebula-commander repository’s flake.nix exposes nixosModules.default for the server (this page) plus packages.default/backend/frontend. It also exposes nixosModules.client and packages.ncclient for the device client — see ncclient Installation: NixOS if you also want to run the client declaratively, on this host or another.

1. Add nebula-commander as an input

In your system flake (e.g. flake.nix in /etc/nixos or your config directory):

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
    nebula-commander.url = "github:NixRTR/nebula-commander";
  };

  outputs = { self, nixpkgs, nebula-commander, ... }: {
    nixosConfigurations.yourHost = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";   # or "aarch64-linux" for ARM, etc.
      modules = [
        ./configuration.nix
        nebula-commander.nixosModules.default
      ];
    };
  };
}

If the repository does not yet have a flake.nix at the ref you’re pinning, use the path-based import above instead.

2. Enable the service and optional package

In your configuration.nix (or in the flake’s module list):

services.nebula-commander.enable = true;
# If the flake provides a package, point the service at it:
# services.nebula-commander.package = nebula-commander.packages.${pkgs.system}.default;

Then rebuild: nixos-rebuild switch --flake .#yourHost (or your usual flake command).


Server options

All options live under services.nebula-commander:

OptionTypeDefaultDescription
enablebool—Enable the Nebula Commander service
packagepackagebackend source from repoNebula Commander package (backend source). With path-based import, built from ../backend relative to the module file.
portport8080Port for the HTTP API when using nginx
backendPortport8081Port for the FastAPI backend (internal)
databasePathstring/var/lib/nebula-commander/db.sqliteSQLite database file path
certStorePathstring/var/lib/nebula-commander/certsDirectory for CA and host certificates
jwtSecretFilenull or pathnullPath to JWT secret file (e.g. managed by sops-nix). If null, a oneshot service generates /var/lib/nebula-commander/jwt-secret on first boot.
encryptionKeyFilenull or pathnullPath to the Fernet encryption-at-rest key (e.g. managed by sops-nix). If null, a oneshot service generates /var/lib/nebula-commander/encryption-key on first boot.
debugboolfalseEnable debug mode
publicUrlnull or stringnullPublic URL of this instance (e.g. https://nebula.example.com). Derives the OIDC redirect URI and is used for redirect validation.
standaloneAdminBootstrapboolfalseAllow the unauthenticated dev-token admin bootstrap endpoint when no OIDC provider is configured. Must be explicitly opted into.
corsOriginsstring"*"CORS origins: "*" or a comma-separated list.
sessionHttpsOnlyboolfalseSet to true in production when served over HTTPS.
allowedRedirectHostsstring""Comma-separated allowed hosts for OAuth/OIDC redirects. Empty derives from oidc.redirectUri/publicUrl.
oidc.issuerUrlnull or stringnullOIDC issuer URL (e.g. a Keycloak realm URL). Leave null to use dev-token auth instead.
oidc.publicIssuerUrlnull or stringnullPublic-facing OIDC issuer URL for browser redirects (logout, etc.), if different from issuerUrl.
oidc.clientIdnull or stringnullOIDC client ID.
oidc.clientSecretFilenull or pathnullPath to a file containing the OIDC client secret (e.g. managed by sops-nix).
oidc.redirectUrinull or stringnullOIDC redirect URI. If unset and publicUrl is set, derived as publicUrl + /api/auth/callback.
oidc.scopesstring"openid profile email"OIDC scopes to request.

The module creates a nebula-commander system user and group, tmpfiles for data directories, and oneshot services that generate the JWT secret and encryption key when their *File options are left null. The main service runs uvicorn with the backend and passes all of the above through as NEBULA_COMMANDER_* environment variables.

For settings not yet exposed as NixOS options, extend the service environment in your config; the backend reads any NEBULA_COMMANDER_* variable from the environment — see Environment variables for the full list.

Looking to run the device client (ncclient) declaratively on NixOS instead of (or alongside) this server? See ncclient Installation: NixOS.

3 - Reverse Proxy

Nebula Commander is typically run behind a reverse proxy such as Nginx, Traefik, or Caddy. The frontend and backend containers listen on plain HTTP inside Docker; your reverse proxy:

  • Terminates TLS on port 443 with a certificate
  • Proxies requests to the frontend container (and through it to the backend)
  • Adds security headers like Strict-Transport-Security (HSTS)

This page shows recommended HSTS settings and example reverse proxy configurations.

HSTS and HTTPS

For best security, configure HTTPS and HSTS on your reverse proxy, not inside the containers. That lets you choose the right TLS policy for your environment (LAN-only, VPN-only, or Internet-facing).

Recommended baseline HSTS header:

Strict-Transport-Security: max-age=31536000

For public Internet deployments on a DNS-backed domain, you can include subdomains:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Avoid preload and avoid HSTS on bare IPs or non-public hostnames:

  • HSTS preload lists require a public, DNS-backed domain
  • HSTS on an IP or "internal-only" hostname can cause problems if it is repurposed later

The examples below assume you already have TLS certificates (for example from Let’s Encrypt) and that the Nebula Commander frontend container is reachable as frontend:80 on a Docker network.

Nginx example

Minimal Nginx configuration to put nebula.example.com behind HTTPS with HSTS:

server {
    listen 443 ssl http2;
    server_name nebula.example.com;

    ssl_certificate     /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # HSTS: adjust policy for your environment
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass http://frontend:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Change:

  • nebula.example.com to your actual hostname
  • Certificate paths to where your certs are stored
  • frontend:80 if you renamed the frontend service or are not using Docker networking

Traefik (file or dynamic configuration)

Traefik can apply HSTS via a headers middleware and route HTTPS traffic to the frontend service. Example dynamic configuration (YAML) using the websecure entrypoint:

http:
  middlewares:
    hsts-headers:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true

  routers:
    nebula:
      rule: "Host(`nebula.example.com`)"
      entryPoints: ["websecure"]
      service: nebula-frontend
      middlewares:
        - hsts-headers
      tls:
        certResolver: letsencrypt

  services:
    nebula-frontend:
      loadBalancer:
        servers:
          - url: "http://frontend:80"

Adapt:

  • nebula.example.com to your hostname
  • websecure and letsencrypt to your Traefik entrypoint and certresolver names
  • frontend:80 to match your frontend container name and port

Traefik with Docker labels

If you use the Nebula Commander Docker compose stack and Traefik’s Docker provider, you can attach labels directly to the frontend service. Example:

  frontend:
    image: ghcr.io/nixrtr/nebula-commander-frontend:latest
    container_name: nebula-commander-frontend
    restart: unless-stopped
    networks:
      - nebula-commander
      - traefik
    labels:
      - "traefik.enable=true"

      # Router: HTTPS entrypoint and host rule
      - "traefik.http.routers.nebula.rule=Host(`nebula.example.com`)"
      - "traefik.http.routers.nebula.entrypoints=websecure"
      - "traefik.http.routers.nebula.tls.certresolver=letsencrypt"

      # Service: forward to container port 80
      - "traefik.http.services.nebula-frontend.loadbalancer.server.port=80"

      # HSTS middleware
      - "traefik.http.middlewares.nebula-hsts.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.nebula-hsts.headers.stsIncludeSubdomains=true"

      # Attach middleware to router
      - "traefik.http.routers.nebula.middlewares=nebula-hsts"

You must also ensure Traefik is on the same Docker network as the frontend service (for example a shared traefik network).

Rename router (nebula), service (nebula-frontend), middleware (nebula-hsts), entrypoint (websecure), and certresolver (letsencrypt) to fit your existing Traefik setup.

Caddy example

Caddy can automatically obtain certificates and proxy to the frontend container:

nebula.example.com {
    reverse_proxy frontend:80

    header {
        Strict-Transport-Security "max-age=31536000"
    }
}

Change nebula.example.com and frontend:80 as needed. Caddy will handle TLS certificates for you when properly configured with DNS and ports 80/443 exposed.