Skip to content

Docker installation

The files below install version 5.x from published Docker Hub images. Create a docker/prod directory, save docker-compose.yml and .env there, and put the database script at docker/prod/db/init-multiple-dbs.sh. You do not need the application source or a local build. Use the same published 5.x tag for all four application images.

services:
db:
image: postgres:17
container_name: db
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init-multiple-dbs.sh:/docker-entrypoint-initdb.d/init-multiple-dbs.sh
restart: unless-stopped
vault:
image: hashicorp/vault:latest
container_name: vault
environment:
VAULT_DEV_ROOT_TOKEN_ID: ${VAULT_TOKEN}
VAULT_ADDR: http://0.0.0.0:8200
volumes:
- vault_data:/vault/file
restart: unless-stopped
n8n:
image: n8nio/n8n:stable
container_name: n8n
ports:
- "5678:5678"
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- N8N_ENDPOINT_WEBHOOK=true
- N8N_PROTOCOL=http
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=db
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=${DB_USER}
- DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
- N8N_METRICS=true
- N8N_LOG_LEVEL=info
- N8N_HOST=http://n8n:5678
- WEBHOOK_URL=http://n8n:5678/
- GENERIC_TIMEZONE=UTC
depends_on:
- db
restart: unless-stopped
volumes:
- n8n_data:/home/node/.n8n
directory:
image: sysadminanywhere/directory:${APP_IMAGE_TAG}
container_name: directory
environment:
LDAP_SERVER: ${LDAP_SERVER}
LDAP_PORT: ${LDAP_PORT}
LDAP_USE_SSL: ${LDAP_USE_SSL}
LDAP_VERIFY_CERTIFICATE: ${LDAP_VERIFY_CERTIFICATE:-false}
LDAP_ADMIN_GROUP_DNS: ${LDAP_ADMIN_GROUP_DNS:-}
LDAP_ADMIN_USERS: ${LDAP_ADMIN_USERS:-}
LDAP_WMI_READ_USERS: ${LDAP_WMI_READ_USERS:-}
DIRECTORY_AUDIT_JOURNAL_PATH: ${DIRECTORY_AUDIT_JOURNAL_PATH:-/data/change-journal.json}
DIRECTORY_AUDIT_JOURNAL_MAX_ENTRIES: ${DIRECTORY_AUDIT_JOURNAL_MAX_ENTRIES:-10000}
API_TOKENS_CONFIG_PATH: ${API_TOKENS_CONFIG_PATH:-/data/api-tokens.json}
VAULT_URI: http://vault:8200
VAULT_TOKEN: ${VAULT_TOKEN}
JWT_SECRET: ${JWT_SECRET}
SPRING_PROFILES_ACTIVE: prod
depends_on:
- vault
restart: unless-stopped
volumes:
- directory_change_journal:/data
inventory:
image: sysadminanywhere/inventory:${APP_IMAGE_TAG}
container_name: inventory
environment:
DB_ADDRESS: db
DB_PORT: 5432
DB_BASE: inventory
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DIRECTORY_SERVICE: http://directory:8080
DIRECTORY_AUTH_MAX_ATTEMPTS: ${DIRECTORY_AUTH_MAX_ATTEMPTS:-5}
DIRECTORY_AUTH_RETRY_DELAY_MS: ${DIRECTORY_AUTH_RETRY_DELAY_MS:-3000}
SCAN_CRON: ${INVENTORY_SCAN_CRON}
LDAP_USERNAME: ${INVENTORY_USERNAME}
LDAP_PASSWORD: ${INVENTORY_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
SPRING_PROFILES_ACTIVE: prod
ports:
- "8082:8080"
restart: unless-stopped
incident:
image: sysadminanywhere/incident:${APP_IMAGE_TAG}
container_name: incident
environment:
DB_ADDRESS: db
DB_PORT: 5432
DB_BASE: incident
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DIRECTORY_SERVICE: http://directory:8080
SCAN_CRON: ${WEF_SCAN_CRON}
WEF_SERVER: ${WEF_SERVER}
WEF_PORT: ${WEF_PORT}
WEF_USERNAME: ${WEF_USERNAME}
WEF_PASSWORD: ${WEF_PASSWORD}
WEF_USE_SSL: ${WEF_USE_SSL}
JWT_SECRET: ${JWT_SECRET}
SPRING_PROFILES_ACTIVE: prod
depends_on:
- db
- directory
restart: unless-stopped
sysadminanywhere:
image: sysadminanywhere/sysadminanywhere:${APP_IMAGE_TAG}
container_name: sysadminanywhere
ports:
- "8080:8080"
environment:
DB_ADDRESS: db
DB_PORT: 5432
DB_BASE: sysadminanywhere
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DIRECTORY_SERVICE: http://directory:8080
INVENTORY_SERVICE: http://inventory:8080
INCIDENT_SERVICE: http://incident:8080
JWT_SECRET: ${JWT_SECRET}
N8N_HOST: http://n8n:5678
N8N_API_KEY: ${N8N_API_KEY}
REPORTS_SCHEDULER_CONFIG_PATH: ${REPORTS_SCHEDULER_CONFIG_PATH:-/data/scheduled-reports.json}
REPORTS_SCHEDULER_RUNS_PATH: ${REPORTS_SCHEDULER_RUNS_PATH:-/data/scheduled-report-runs.json}
REPORTS_SCHEDULER_OUTPUT_DIR: ${REPORTS_SCHEDULER_OUTPUT_DIR:-/data/scheduled-reports}
WEBHOOKS_CONFIG_PATH: ${WEBHOOKS_CONFIG_PATH:-/data/webhooks.json}
MAIL_HOST: ${MAIL_HOST:-}
MAIL_PORT: ${MAIL_PORT:-587}
MAIL_USERNAME: ${MAIL_USERNAME:-}
MAIL_PASSWORD: ${MAIL_PASSWORD:-}
MAIL_SMTP_AUTH: ${MAIL_SMTP_AUTH:-true}
MAIL_SMTP_STARTTLS: ${MAIL_SMTP_STARTTLS:-true}
SPRING_PROFILES_ACTIVE: prod
ALLOWED_USERS:
ALLOWED_GROUPS:
depends_on:
- db
- directory
- inventory
- incident
volumes:
- report_scheduler_data:/data
restart: unless-stopped
volumes:
postgres_data:
vault_data:
n8n_data:
directory_change_journal:
report_scheduler_data:

Save the following example as .env alongside the Compose file. Before starting, set APP_IMAGE_TAG to the actual published 5.x release tag, configure LDAP, inventory and WEF accounts, and replace all example passwords and tokens. Keep .env private.

APP_IMAGE_TAG=5.0.0
DB_NAME=sysadminanywhere
DB_USER=postgres
DB_PASSWORD=change_me
VAULT_TOKEN=change_me
JWT_SECRET=change_me
LDAP_SERVER=dc.example.local
LDAP_PORT=389
LDAP_USE_SSL=false
# false keeps legacy trust-any behavior; set true to validate the LDAP certificate
LDAP_VERIFY_CERTIFICATE=false
# Direct AD group DN(s) allowed to administer; separate multiple DNs with semicolons.
# Empty defaults to CN=Domain Admins,CN=Users,<domain base DN>.
LDAP_ADMIN_GROUP_DNS=
# Optional exact login names, comma-separated.
LDAP_ADMIN_USERS=
# Inventory account may run read-only WMI queries, but not commands or invocations.
LDAP_WMI_READ_USERS=readonly
# JSON file for the application-level change journal
DIRECTORY_AUDIT_JOURNAL_PATH=/data/change-journal.json
DIRECTORY_AUDIT_JOURNAL_MAX_ENTRIES=10000
INVENTORY_SCAN_CRON=0 */30 * * * *
INVENTORY_USERNAME=readonly
INVENTORY_PASSWORD=change_me
DIRECTORY_AUTH_MAX_ATTEMPTS=5
DIRECTORY_AUTH_RETRY_DELAY_MS=3000
WEF_SCAN_CRON=0 */5 * * * *
WEF_SERVER=wef.example.local
WEF_PORT=5985
WEF_USERNAME=wef_user
WEF_PASSWORD=change_me
WEF_USE_SSL=false
N8N_USER=admin
N8N_PASSWORD=change_me
N8N_API_KEY=change_me
# Scheduled report files and optional SMTP delivery
REPORTS_SCHEDULER_CONFIG_PATH=/data/scheduled-reports.json
REPORTS_SCHEDULER_RUNS_PATH=/data/scheduled-report-runs.json
REPORTS_SCHEDULER_OUTPUT_DIR=/data/scheduled-reports
WEBHOOKS_CONFIG_PATH=/data/webhooks.json
API_TOKENS_CONFIG_PATH=/data/api-tokens.json
MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true
AI_ENABLED=true
AI_PROVIDER=openrouter
AI_BASE_URL=https://openrouter.ai/api/v1
AI_API_KEY=or-v1-change_me
AI_MODEL=google/gemma-3-27b-it:free
AI_APP_URL_MAIN=http://localhost:8080
AI_APP_URL_INCIDENT=http://localhost:8083
AI_APP_NAME_MAIN=SysadminAnywhereMain
AI_APP_NAME_INCIDENT=SysadminAnywhereIncident

Create a db directory next to the Compose file and save init-multiple-dbs.sh in it:

#!/bin/bash
set -e
for db in incident inventory n8n; do
echo "Creating database '$db'"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<EOSQL
CREATE DATABASE $db;
EOSQL
done

From docker/prod, pull the images and start the services:

Terminal window
docker compose pull
docker compose up -d

Open http://localhost:8080/login on the host and sign in with your Active Directory account. If a service is unavailable, check docker compose logs and the administrator page /settings/dependencies.

PostgreSQL is pinned to version 17. Vault and n8n use floating tags; Vault runs in development mode in this example. Replace it with a production configuration and review ports, persistent storage, and TLS before production use. For LDAPS, add the issuing CA to the runtime trust store and set LDAP_USE_SSL=true and LDAP_VERIFY_CERTIFICATE=true.