Skip to content

Docker Compose Guide

DartSteven edited this page Aug 24, 2026 · 2 revisions

Docker Compose Guide

This page documents the secure Compose profiles shipped with Nutify 0.3.0.

The default profile supports remote NUT servers and directly attached USB HID UPS devices. Stable serial or non-USB device paths use an explicit scoped override. Environment-managed OIDC is a separate advanced override so the simple profile stays easy to start and maintain.

1) Choose the Correct Image

Host Image tag
AMD64/x86_64 dartsteven/nutify:latest-amd64
Apple Silicon dartsteven/nutify:latest-mac-arm64
Raspberry Pi 3, 32-bit OS dartsteven/nutify:latest-raspberrypi3-armv7
Raspberry Pi 4, 32-bit OS dartsteven/nutify:latest-raspberrypi4-armv7
Raspberry Pi 4/5, 64-bit OS dartsteven/nutify:latest-raspberrypi5-arm64

Raspberry Pi 3 requires a 32-bit operating system for the supported ARMv7 image.

2) Default Security Model

The supplied docker-compose.yaml does not:

  • use privileged: true
  • mount the complete host /dev tree
  • grant SYS_ADMIN, SYS_RAWIO, or MKNOD
  • include a default or hardcoded SECRET_KEY

It drops all capabilities, adds only the capabilities needed during startup and service supervision, enables no-new-privileges, and runs the web application as the unprivileged nut user. USB support is constrained to character-device major 189 and /dev/bus/usb; udev metadata is mounted read-only. Startup grants the nut runtime user only the numeric groups found on those USB device nodes and never changes host device ownership or modes.

Use this default for network targets and USB HID targets detected by nut-scanner.

3) Create the Runtime Secret

Copy the example file and generate a unique secret:

cp .env.example .env
printf 'SECRET_KEY=%s\n' "$(openssl rand -hex 32)" > .env

Keep .env private. Do not commit it and do not change SECRET_KEY between restarts. Existing encrypted provider credentials and password recovery depend on this key.

Optional exact reverse-proxy origins can be added later:

SOCKETIO_ALLOWED_ORIGINS=https://nutify.example.com,https://nutify.example.net

Wildcards are rejected. A correctly configured proxy should still forward WebSocket upgrades; Nutify uses authenticated HTTP snapshots when Socket.IO is temporarily unavailable.

4) Secure Base Profile

Repository file: docker-compose.yaml

services:
  nut:
    image: dartsteven/nutify:latest-amd64
    container_name: Nutify

    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - DAC_OVERRIDE
      - FOWNER
      - KILL
      - SETGID
      - SETUID
    security_opt:
      - no-new-privileges:true

    device_cgroup_rules:
      - "c 189:* rwm"

    volumes:
      - ./Nutify/logs:/app/nutify/logs
      - ./Nutify/instance:/app/nutify/instance
      - ./Nutify/ssl:/app/ssl
      - ./Nutify/etc/nut:/etc/nut
      - /dev/bus/usb:/dev/bus/usb:rw
      - /run/udev:/run/udev:ro

    environment:
      SECRET_KEY: ${SECRET_KEY:?Set SECRET_KEY in .env}
      NUTIFY_WEB_USER: nut
      NUT_SERVICE_USER: nut
      UDEV: "1"
      SKIP_PERMCHECK: "true"
      NUTIFY_USB_GID: ${NUTIFY_USB_GID:-}
      LOG: "true"
      LOG_LEVEL: INFO
      LOG_WERKZEUG: "true"
      ENABLE_LOG_STARTUP: Y
      SSL_ENABLED: "false"
      SOCKETIO_ALLOWED_ORIGINS: ${SOCKETIO_ALLOWED_ORIGINS:-}

    ports:
      - "3493:3493"
      - "5050:5050"
      - "443:443"

    restart: always

Use the complete repository file rather than copying only this explanatory excerpt.

5) Choose the Authentication Profile

Simple: Local Login and Guided SSO

The default docker-compose.yaml starts with Nutify local authentication. Run:

docker compose up -d

An administrator can later configure optional OIDC from System -> Authentication. This stores the encrypted configuration in Nutify's database and requires no Compose changes.

Advanced: Environment-Managed OIDC

Repository override: docker-compose.oidc.yaml

Use this profile when deployment automation must own the OIDC configuration. Add the required values to the private .env file:

OIDC_ISSUER=https://sso.example.com/application/o/nutify/
OIDC_CLIENT_ID=nutify
OIDC_CLIENT_SECRET=replace-with-provider-secret
OIDC_REDIRECT_URI=https://nutify.example.com/auth/oidc/callback
OIDC_ADMIN_GROUP=nutify-admins
OIDC_USER_GROUP=nutify-users

Start the base file and advanced override together:

docker compose \
  -f docker-compose.yaml \
  -f docker-compose.oidc.yaml \
  up -d

This sets OIDC_CONFIG_SOURCE=environment, makes the web Authentication page read-only, requires HTTPS, requires an explicit administrator group, and keeps OIDC_ALLOW_ALL_USERS=false. Local recovery remains available at /auth/login?local=1.

6) Open Nutify

Then open http://<host>:5050 and complete the setup wizard.

Useful commands:

docker compose ps
docker compose logs -f
docker compose down

7) Optional Direct Hardware Access

This override is unrelated to Nutify's Single Monitor and Multi Monitor profiles. Monitoring profile selection happens in the Wizard and controls target behavior. Hardware exposure happens at the Docker boundary before the container starts.

The Wizard can select a NUT driver and device port, but it cannot add a host device to an already running container or change Docker device-cgroup rules. Use docker-compose.hardware.yaml only when the NUT driver inside Nutify must open a stable serial or non-USB path. It is unnecessary for USB HID devices and remote or host-managed NUT servers.

For one directly attached serial UPS, set stable device values in .env:

NUTIFY_DEVICE=/dev/serial/by-id/your-ups-device
NUTIFY_DEVICE_CONTAINER=/dev/nutify-ups
NUTIFY_DEVICE_GID=20

NUTIFY_DEVICE_GID must be the numeric host group that owns the device. Start with the scoped override:

docker compose \
  -f docker-compose.yaml \
  -f docker-compose.hardware.yaml \
  up -d

Configure the NUT driver port as /dev/nutify-ups. The override exposes only the selected device; it does not expose all host devices. This narrower permission is why the hardware override exists instead of enabling privileged mode in the default Compose profile.

A stable udev alias can be created on Linux:

SUBSYSTEM=="tty", ATTRS{idVendor}=="VVVV", ATTRS{idProduct}=="PPPP", SYMLINK+="nutify-ups", GROUP="dialout", MODE="0660"

Replace VVVV and PPPP with values from udevadm info.

8) USB HID Devices

The base Compose file enables the Wizard's Auto-detect with nut-scanner and manual Detected USB Port picker. It mounts only /dev/bus/usb, permits only Linux USB character devices (major 189), and mounts /run/udev read-only. It does not use privileged mode, map all of /dev, or grant hardware-management capabilities.

Nutify detects the host USB device GIDs at startup and adds only its unprivileged runtime users to matching container groups. If startup logs report that no USB GID was found, set the numeric owner group explicitly in .env:

NUTIFY_USB_GID=46

Find the value on the Linux host with stat -c '%g' /dev/bus/usb/BBB/DDD. GID 0 is rejected: never add the application user to the root group. If the device is owned by root:root, install the host NUT udev rules or create a dedicated group/rule with mode 0660, then set NUTIFY_USB_GID to that group's numeric ID. Recreate the container after changing .env. If Docker itself cannot expose the host USB bus (for example, some non-Linux Docker Desktop environments), run NUT on a Linux host and use Network Client mode.

9) Persistent Data

Host path Container path Purpose
./Nutify/logs /app/nutify/logs Application logs
./Nutify/instance /app/nutify/instance SQLite DB and runtime state
./Nutify/ssl /app/ssl TLS certificate/key
./Nutify/etc/nut /etc/nut Generated NUT configuration
/dev/bus/usb /dev/bus/usb USB HID discovery and driver access
/run/udev /run/udev Read-only USB identity metadata

Back up these directories before replacing an image or changing topology.

10) Update and Rollback

Update:

docker compose pull
docker compose up -d

Rollback by restoring the previous image tag and running docker compose up -d. Validate login, target connectivity, Event Matrix routes, and report delivery after either operation.

Nutify 0.3.0 is not database-compatible with previous releases. A clean instance directory is required when crossing that boundary.

11) First-Run Validation

  1. Open http://<host>:5050.
  2. Create the first administrator.
  3. Select the correct Single/Multi topology.
  4. Test and save the target connection.
  5. Confirm target telemetry in the TopBar and dashboard.
  6. Configure Provider entries globally, then Event Matrix/Reporter per target.

12) Troubleshooting

Container does not start:

  • confirm .env contains non-empty SECRET_KEY
  • confirm selected image tag matches host architecture
  • inspect docker compose logs

Remote UPS does not connect:

  • confirm host, port, UPS identifier, monitor username, and password
  • confirm port 3493 is reachable from the container
  • confirm remote upsd ACL allows the container network

Direct device fails:

  • for USB HID, confirm /dev/bus/usb exists in the container and inspect startup logs for the detected USB GID
  • if automatic USB GID detection fails, set NUTIFY_USB_GID to the numeric host group and recreate the container
  • for serial/non-USB, confirm the stable host path and NUTIFY_DEVICE_GID
  • confirm driver port equals NUTIFY_DEVICE_CONTAINER
  • confirm no host service already owns the serial device

12) Security Checklist

  • use a random, persistent SECRET_KEY
  • use strong, unique administrator credentials
  • expose only required ports to trusted networks
  • use exact reverse-proxy origins, never wildcards
  • expose at most one required hardware device
  • never add privileged mode or broad host device mounts
  • rotate SMTP, ntfy, Telegram, and webhook credentials after exposure

Clone this wiki locally