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.
docker-compose.yml
Section titled “docker-compose.yml”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=sysadminanywhereDB_USER=postgresDB_PASSWORD=change_me
VAULT_TOKEN=change_meJWT_SECRET=change_me
LDAP_SERVER=dc.example.localLDAP_PORT=389LDAP_USE_SSL=false# false keeps legacy trust-any behavior; set true to validate the LDAP certificateLDAP_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 journalDIRECTORY_AUDIT_JOURNAL_PATH=/data/change-journal.jsonDIRECTORY_AUDIT_JOURNAL_MAX_ENTRIES=10000
INVENTORY_SCAN_CRON=0 */30 * * * *INVENTORY_USERNAME=readonlyINVENTORY_PASSWORD=change_meDIRECTORY_AUTH_MAX_ATTEMPTS=5DIRECTORY_AUTH_RETRY_DELAY_MS=3000
WEF_SCAN_CRON=0 */5 * * * *WEF_SERVER=wef.example.localWEF_PORT=5985WEF_USERNAME=wef_userWEF_PASSWORD=change_meWEF_USE_SSL=false
N8N_USER=adminN8N_PASSWORD=change_meN8N_API_KEY=change_me
# Scheduled report files and optional SMTP deliveryREPORTS_SCHEDULER_CONFIG_PATH=/data/scheduled-reports.jsonREPORTS_SCHEDULER_RUNS_PATH=/data/scheduled-report-runs.jsonREPORTS_SCHEDULER_OUTPUT_DIR=/data/scheduled-reportsWEBHOOKS_CONFIG_PATH=/data/webhooks.jsonAPI_TOKENS_CONFIG_PATH=/data/api-tokens.jsonMAIL_HOST=MAIL_PORT=587MAIL_USERNAME=MAIL_PASSWORD=MAIL_SMTP_AUTH=trueMAIL_SMTP_STARTTLS=true
AI_ENABLED=trueAI_PROVIDER=openrouterAI_BASE_URL=https://openrouter.ai/api/v1AI_API_KEY=or-v1-change_meAI_MODEL=google/gemma-3-27b-it:freeAI_APP_URL_MAIN=http://localhost:8080AI_APP_URL_INCIDENT=http://localhost:8083AI_APP_NAME_MAIN=SysadminAnywhereMainAI_APP_NAME_INCIDENT=SysadminAnywhereIncidentdb/init-multiple-dbs.sh
Section titled “db/init-multiple-dbs.sh”Create a db directory next to the Compose file and save init-multiple-dbs.sh in it:
#!/bin/bashset -e
for db in incident inventory n8n; do echo "Creating database '$db'" psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<EOSQLCREATE DATABASE $db;EOSQLdoneStart the application
Section titled “Start the application”From docker/prod, pull the images and start the services:
docker compose pulldocker compose up -dOpen 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.