Skip to content

Docker Deployment (Enterprise)

The ScreenStop Dashboard is available as a Docker image for enterprise deployments, including air-gapped and containerized environments.

Current default vs. OCI-native (roadmap)

This guide describes the portable default deployment, which is platform-agnostic. For Oracle Cloud (OCI) deployments, the following are tracked on the near-term roadmap and are not reflected in the steps below:

  • Secrets management: this guide stores DASHBOARD_SECRET_KEY / STATION_API_TOKEN in a .env file. OCI Vault / KMS-backed secrets (no plaintext env vars) are the committed target for OCI deployments.
  • Encryption at rest: frame encryption with a pluggable key provider is available today (SCREENSTOP_FRAME_ENCRYPTION); OCI Vault as the key provider is the OCI-native target.
  • MFA: TOTP-based multi-factor authentication for admin accounts.

Treat the secrets/Vault/MFA items as incoming for the OCI release — see the CSSAP responses for the authoritative commitment and target dates.


What's Included

Each release ships a versioned Docker tar file and SBOM:

File Description
screenstop-<version>.tar Docker image — load with docker load
screenstop-<version>-sbom.json CycloneDX Software Bill of Materials

You will also create two small deployment files on the host (docker-compose.yml and nginx.conf) — both are provided in Step 2 below.


Prerequisites

  • Docker Engine 20.10+
  • Docker Compose v2
  • SSL certificate + private key (cert.pem, key.pem)

Step 1 — Load the Image

Transfer the tar file to your server, then load it:

docker load < screenstop-2.0.2.tar

Verify it loaded:

docker image ls | grep screenstop

Step 2 — Create the Deployment Files

In your deployment directory, create docker-compose.yml. Note image: — you run the pre-built image you loaded, you do not build from source:

services:
  screenstop:
    image: screenstop:2.0.2
    container_name: screenstop
    expose:
      - "8080"
    volumes:
      - screenstop_data:/app/data
    environment:
      - DASHBOARD_SECRET_KEY=${DASHBOARD_SECRET_KEY}
      - STATION_API_TOKEN=${STATION_API_TOKEN}
      - SCREENSTOP_DEV_MODE=${SCREENSTOP_DEV_MODE:-false}
      - DASHBOARD_DB_PATH=/app/data/dashboard.db
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    container_name: screenstop-nginx
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ${CERTS_DIR:-./certs}:/etc/nginx/certs:ro
    depends_on:
      - screenstop
    restart: unless-stopped

volumes:
  screenstop_data:

And nginx.conf (terminates TLS, redirects HTTP→HTTPS, rate-limits login):

limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_status 429;

server {
    listen 443 ssl;
    ssl_certificate     /etc/nginx/certs/cert.pem;
    ssl_certificate_key /etc/nginx/certs/key.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location /auth/login {
        limit_req          zone=login burst=5 nodelay;
        proxy_pass         http://screenstop:8080;
        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 https;
    }

    location / {
        proxy_pass         http://screenstop:8080;
        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 https;
    }
}

server {
    listen 80;
    return 301 https://$host$request_uri;
}

Editing nginx.conf later

nginx.conf is bind-mounted as a single file. If you edit it, run docker compose restart nginx so the change is picked up (a reload alone may keep serving the old file).


Step 3 — Prepare Certificates

Place your TLS certificate and private key in a directory on the host:

/opt/screenstop/certs/
├── cert.pem
└── key.pem

Self-signed certificate (testing only)

For non-production testing, generate a self-signed cert:

openssl req -x509 -newkey rsa:2048 \
  -keyout /opt/screenstop/certs/key.pem \
  -out /opt/screenstop/certs/cert.pem \
  -days 365 -nodes -subj "/CN=screenstop"


Step 4 — Configure Environment

Create a .env file alongside docker-compose.yml:

DASHBOARD_SECRET_KEY=<strong-random-secret>
STATION_API_TOKEN=<pre-shared-token-for-agents>
CERTS_DIR=/opt/screenstop/certs
Variable Description
DASHBOARD_SECRET_KEY Session signing secret — generate with openssl rand -hex 32
STATION_API_TOKEN Token agents use to authenticate with the server
CERTS_DIR Host path to the directory containing cert.pem and key.pem

Step 5 — Start the Container

docker compose up -d

The dashboard will be available at https://<server-ip>/ — the root automatically redirects to the dashboard UI at /app/.

HTTP (port 80) redirects automatically to HTTPS (port 443).


Step 6 — Configure Agent Endpoints

On each protected workstation, set the dashboard URL in setup_parameters.json (place this file in the install directory before or after deployment — IT/MDM):

{
    "dashboard_url": {
        "value": "https://<server-ip>",
        "override": true
    },
    "dashboard_api_token": {
        "value": "<pre-shared-token>",
        "override": true
    }
}

The agent picks up the file automatically on next startup.


Step 7 — Bootstrap Admin Account

On a fresh deployment the database has no users. Dev mode provides a temporary built-in admin/admin login used once to create your real admin account — you then change it and turn dev mode off. ScreenStop never ships or transmits a password; you set your own here.

Do this immediately — before exposing the dashboard

The temporary admin/admin login is active only while dev mode is on. Perform this bootstrap right after deployment, on a closed/internal network, before the dashboard is reachable by users. Then:

  1. Create your real admin with a strong password (Step 3 below), and
  2. Disable dev mode (Step 4) so admin/admin no longer works.

Never leave the dashboard exposed with dev mode on — anyone who reaches it could log in as admin/admin.

1. Start with dev mode enabled (temporary):

In your .env file, add:

SCREENSTOP_DEV_MODE=true

Restart the container:

docker compose down && docker compose up -d

2. Log in at https://<server-ip>/ (redirects to the dashboard at /app/) with the temporary credentials:

Username Password
admin admin

3. Go to Admin → Users → Create User and create your real admin account:

  • Username must be a valid email address (e.g. admin@yourcompany.com)
  • Password must be at least 12 characters with uppercase, lowercase, a number, and a special character

4. Disable dev mode — remove SCREENSTOP_DEV_MODE=true from .env and restart:

docker compose down && docker compose up -d

Warning

Never leave SCREENSTOP_DEV_MODE=true in production. It bypasses the database entirely and allows login with admin/admin.


Persistent Data

Station data, events, and configuration are stored in a named Docker volume (screenstop_data). This persists across container restarts and upgrades.

To back up:

docker run --rm \
  -v screenstop_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/screenstop-backup-$(date +%Y%m%d).tar.gz /data

Upgrading

Each new release is delivered as a release bundle containing the image tar, a matching docker-compose.yml, a checksum, the upgrade script, and these notes.

From your deployment directory, run the bundled script with the new image tar:

bash update_docker.sh screenstop-<new-version>.tar

The script will:

  1. Back up the database (and the current docker-compose.yml) to ./backups/
  2. Verify the image checksum (if screenstop-<new-version>.tar.sha256 is present)
  3. Load the new image and point Compose at the new version
  4. Restart the stack — your data volume is preserved and database migrations run automatically
  5. Health-check the dashboard, and automatically roll back to the previous version if the new one fails to start cleanly

Configurable paths

The script reads sensible defaults but every path is overridable via environment variables (SCREENSTOP_DEPLOY_DIR, SCREENSTOP_COMPOSE_FILE, SCREENSTOP_HEALTH_URL, …) for non-standard installs.

Dashboard — manual fallback

If you prefer to upgrade by hand:

# 1. Back up the database first
docker cp screenstop:/app/data/dashboard.db ./dashboard-backup-$(date +%F).db
# 2. Load the new image
docker load < screenstop-<new-version>.tar
# 3. Point docker-compose.yml at the new image tag (use the bundled compose file)
# 4. Restart
docker compose down && docker compose up -d

The data volume is preserved automatically and schema migrations run on startup.

Agent (workstations)

The endpoint agent is updated the same way it was deployed — push the new agent installer through your MDM (Intune, Jamf, SCCM, Kandji, …). The new installer reinstalls over the old agent and preserves the existing setup_parameters.json (dashboard URL + token), so no per-machine reconfiguration is needed. See Install on Windows / Install on macOS for the MDM deployment steps.

Upgrade order

Upgrade the dashboard first, then roll out the agent update. The dashboard is backward-compatible with the previous agent version, so a brief version skew during rollout is safe.


Security Notes

  • The dashboard container runs as a non-root user (screenstop)
  • All communication is TLS 1.2+ (nginx terminates TLS, proxies to FastAPI internally)
  • Port 8080 is container-internal only. In docker-compose.yml it uses Docker expose: (reachable only on the internal Docker network between nginx and the app), not ports: — so it is never published to the host or reachable from outside. Only 443/80 are host-published.
  • Firewall: only ports 443 and 80 need to be open inbound