Skip to content

Docker Compose installation

Docker Compose is the recommended installation method.

Host requirements

Minimum for a small homelab instance:

ResourceMinimumRecommended
CPU1 vCPU2 vCPU
RAM512 MB1 GB+
Storage1 GB freeSSD with several GB free, depending on log volume and retention

OpenSecDash is lightweight, but storage usage depends on imported event volume, configured retention, and debug/log output.

The release gate exercises these resource profiles against the published container shape. They are validation boundaries, not promises for every plugin mix or storage device:

ProfileEventsCPU limitRAM limitExpected use
Fresh01 vCPU512 MiBFirst installation and first start
Small10,0001 vCPU512 MiBTypical small homelab
Large1,000,0002 vCPU1 GiBDocumented large local database
Upgrade10,000 synthetic legacy events2 vCPU1 GiBFull migration and startup compatibility

The checks cover startup, read-only readiness, bounded search, migration, and clean connection shutdown. Real ingestion rates and plugin memory use still depend on log volume and enabled integrations.

As a rough guide, measured on SQLite after VACUUM (events plus their indexes and rollups):

Events currently storedApproximate database size
A few thousand (light homelab use)A few MB
10,000~10 MB
100,000~100 MB
1,000,000~1 GB
10,000,000 (very many)~10 GB

Rule of thumb: ~1 KB per stored event. This is about how many events are currently kept (bounded by the Retention days setting), not how many were ever imported - daily/monthly rollups used for historical charts and dashboards stay tiny (a few KB per day) even after old raw events are cleaned up by retention. A busy, public-facing Traefik access log can easily produce tens of thousands of events a day, so size Retention days and storage accordingly.

Compose file

The repository contains the full Compose example at docker/docker-compose.example.yml. Copy it to docker-compose.yml before starting, or use the examples below. Start minimal and add mounts as you enable plugins.

Minimal example
yaml
services:
  opensecdash:
    image: konkos1/opensecdash:latest
    container_name: opensecdash
    ports:
      - "8765:8000"
    volumes:
      - opensecdash-data:/data
    read_only: true
    tmpfs:
      - /tmp:size=16m,mode=1777
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    pids_limit: 256
    mem_limit: 1g
    cpus: 2.0
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    restart: unless-stopped

volumes:
  opensecdash-data:
Full example (all plugin log mounts)
yaml
services:
  opensecdash:
    image: konkos1/opensecdash:latest
    container_name: opensecdash
    # Lets the CrowdSec plugin reach CrowdSec's Local API on 127.0.0.1 when
    # CrowdSec runs on this same host; see "Plugin file mounts" below.
    network_mode: "host"
    volumes:
      - opensecdash-data:/data
      - /var/log/traefik/access.log:/logs/access.log:ro
      - /var/log/traefik/geoblock.log:/logs/geoblock.log:ro
      - /var/log/crowdsec/crowdsec.log:/logs/crowdsec.log:ro
      - ./assets/assets.json:/assets/assets.json:ro
    read_only: true
    tmpfs:
      - /tmp:size=16m,mode=1777
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    pids_limit: 256
    mem_limit: 1g
    cpus: 2.0
    # Optional: uncomment the environment block and one or more variables to
    # completely disable plugins at startup. Disabled plugins are hidden from
    # Settings, Diagnostics and navigation, and run no background tasks.
    # environment:
    #   OSD_PLUGIN_CROWDSEC_DISABLED: "true"
    #   OSD_PLUGIN_TRAEFIK_LOG_DISABLED: "true"
    #   OSD_PLUGIN_GEOBLOCK_LOG_DISABLED: "true"
    #   OSD_PLUGIN_GEOIP_DISABLED: "true"
    #   OSD_PLUGIN_JSON_ASSETS_DISABLED: "true"
    #   OSD_PLUGIN_PROXMOX_ASSETS_DISABLED: "true"
    #   OSD_PLUGIN_MQTT_DISABLED: "true"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    restart: unless-stopped

volumes:
  opensecdash-data:

With network_mode: "host" the container shares the host's network stack, so the ports mapping is dropped and the app is reachable directly on host port 8000 instead of 8765.

Start the app:

bash
docker compose up -d

Then open OpenSecDash through the hostname your reverse proxy serves:

text
https://dash.example.com

First start of a new installation

A new installation starts with internal sign-in enabled. The first visit shows a one-time setup page that creates the first Admin account; nothing else is reachable until it is finished, apart from /health and /ready.

Configure the reverse proxy before that first visit. The setup can only be completed when the request arrives over HTTPS on external port 443, from a proxy listed explicitly in OSD_TRUSTED_PROXIES, under the hostname you enter:

yaml
environment:
  # The reverse proxy in front of OpenSecDash, as an IP or a small CIDR.
  OSD_TRUSTED_PROXIES: 192.0.2.10

See Reverse proxy and Authentication.

To run OpenSecDash open on purpose instead — behind a VPN, an authentication proxy, or for a quick local trial on http://localhost:8765 — set OSD_AUTH_DISABLED=true and restart. Every visitor who can reach the instance then has full access.

Updating an existing installation does not change its sign-in state. An installation with internal sign-in enabled continues unchanged; one that was open stays reachable and shows a permanent prompt to decide between internal sign-in and the deliberate bypass.

Ports

The app listens on port 8000 inside the container. The example maps it to host port 8765 to avoid common homelab conflicts:

yaml
ports:
  - "8765:8000"

The internal app host/port can also be overridden, but this is rarely needed:

yaml
environment:
  OSD_HOST: 0.0.0.0
  OSD_PORT: "8000"

If you change OSD_PORT, update the port mapping and health check assumptions accordingly.

Persistent data

Persistent data is stored in /data inside the container. Keep this as a named volume or bind mount so the SQLite database survives container replacement:

yaml
volumes:
  - opensecdash-data:/data

The Docker image defaults to:

text
DATABASE_URL=sqlite:////data/opensecdash.db
AUTO_MIGRATE=true
LOG_FILE_ENABLED=false

You normally do not need to set these values in docker-compose.yml.

Optional environment overrides

VariableDefault in Docker imageWhen to change it
DATABASE_URLsqlite:////data/opensecdash.dbUse a different database path or backend. SQLite under /data is recommended for Docker.
AUTO_MIGRATEtrueSet to false only if you want to run Alembic migrations manually before starting the app.
LOG_FILE_ENABLEDfalseEnable only if you intentionally want an app-managed file log inside a mounted path. Docker stdout/stderr logging is recommended.
LOG_FILE_PATHlogs/opensecdash.logFile log path used when file logging is enabled. Mount the parent directory if you need persistence.
LOG_LEVELINFOUse DEBUG temporarily for troubleshooting; it can create much more output.
OSD_HOST0.0.0.0Internal bind address for uvicorn. Usually leave unchanged in Docker.
OSD_PORT8000Internal uvicorn port. Usually leave unchanged and only change the host-side port mapping.
OSD_TRUSTED_PROXIESloopback + private rangesComma-separated IPs/CIDRs of reverse proxies whose X-Forwarded-* headers are trusted. Empty disables processing; * trusts all. Must name the proxy explicitly for internal sign-in. See the reverse proxy guide.
OSD_AUTH_DISABLEDnot setSet to true to run OpenSecDash without internal sign-in, and to recover from a lockout. Every visitor then has full access; protect it another way. See Authentication.

Logging settings are stored in the app database after initial setup. Changing LOG_FILE_ENABLED, LOG_FILE_PATH, or LOG_LEVEL later may not override an already-saved Settings value; use the Settings page for runtime logging changes.

Disabling plugins

Any plugin can be completely disabled with OSD_PLUGIN_<PLUGIN>_DISABLED=true (accepted values 1/true/yes/on). A disabled plugin is not loaded at all: it is hidden from Settings, Diagnostics and the navigation, and runs no background tasks. Its saved settings stay in the database and reappear when the variable is removed.

<PLUGIN> is the plugin's directory name or id, uppercased, with - replaced by _:

PluginVariable
CrowdSecOSD_PLUGIN_CROWDSEC_DISABLED
Traefik Access LogOSD_PLUGIN_TRAEFIK_LOG_DISABLED
GeoBlock LogOSD_PLUGIN_GEOBLOCK_LOG_DISABLED
GeoIP enrichmentOSD_PLUGIN_GEOIP_DISABLED
JSON AssetsOSD_PLUGIN_JSON_ASSETS_DISABLED
Proxmox AssetsOSD_PLUGIN_PROXMOX_ASSETS_DISABLED
MQTT exportOSD_PLUGIN_MQTT_DISABLED (or OSD_PLUGIN_MQTT_HASS_DISABLED)

Plugin file mounts

OpenSecDash is easiest to operate when it can read the relevant log files locally. In many homelab setups that means running the OpenSecDash container on the same Docker host or guest as Traefik, GeoBlock, CrowdSec, and similar tools, then mounting their log files read-only into the container.

For persistent data, both a named Docker volume and a host bind mount such as ./data:/data are supported. On startup, the container fixes /data ownership and then runs the app as the unprivileged opensecdash user.

Volume upgrades and ownership

The Compose hardening keeps the root filesystem read-only, provides a small temporary filesystem at /tmp, drops all capabilities except CHOWN, SETUID, and SETGID, and applies realistic starter limits for a homelab. The container still starts as root only for the ownership repair; the application process is unprivileged. Adjust CPU and memory limits for unusually large event volumes, but keep the other boundaries.

Existing named volumes and writable Linux bind mounts upgrade without a manual step: the entry point recursively repairs /data ownership before migration and startup. If a NAS or bind-mount policy prevents that ownership change, stop the app and run this one-time repair against the same mount before retrying the upgrade:

bash
docker run --rm --user root --entrypoint sh \
  -v /absolute/path/to/data:/data \
  konkos1/opensecdash:latest \
  -c 'chown -R opensecdash:opensecdash /data'

Replace the host path with the actual bind mount. For a named volume, replace the -v value with opensecdash-data:/data. Back up /data first; the command changes ownership, not database contents. Read-only plugin files should remain under /logs or /assets, not below /data.

If those tools run on a different host/VM, you need to make their logs available to OpenSecDash first, for example with bind mounts, shared storage, or another log shipping approach.

Plugins that read local files need those files mounted into the container; see the full Compose example above for the log and assets.json mounts.

These container-side paths (/logs/access.log, /logs/geoblock.log, /logs/crowdsec.log) already match the Traefik, GeoBlock, and CrowdSec plugin defaults, so a fresh install works out of the box once the mounts above are in place. Only change the paths on the Settings page if you mount the logs somewhere else.

assets.json is mounted under a dedicated /assets path rather than under /data: /data is owned and recursively chowned to the unprivileged opensecdash user on container startup, and a read-only file bind-mounted underneath it can't be chowned. Set the JSON Assets plugin's Source setting to /assets/assets.json to match this mount.

CrowdSec ban/unban actions and decision sync use the Local API. It needs no extra mounts, just dedicated credentials entered in Settings (plus network_mode: "host" when CrowdSec runs on the same host); see CrowdSec plugin: Connecting via the Local API.

Docker logging

Docker installs log to stdout/stderr by default and should let Docker rotate logs. Docker log rotation is configured by the Compose logging section because it cannot be baked into the image itself:

yaml
logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

Inspect logs with:

bash
docker compose logs opensecdash --tail=500

The debug ZIP still works when file logging is disabled. In that case, opensecdash-log.txt explains how to collect Docker logs.

Released under the GNU Affero General Public License v3.0. Third-party licenses.