Skip to main content

Deployment & Operations

The on-premises platform is a Docker Compose stack of prebuilt images pulled from a private registry with a read-only token you are given at onboarding. No source code, no build step: pull, configure, start. The same images run the SaaS.

Prerequisites

RequirementMinimumRecommended
HostLinux x86-64 with Docker Engine and Compose v2; the workers need the Docker socket (scanners run as sibling containers)A dedicated VM
CPU / RAM4 vCPU / 8 GB4+ vCPU / 16 GB (MongoDB alone is capped at 6 GB by default)
Disk80 GB100 GB+ — scan artefacts, evidence and backups grow
NetworkOutbound HTTPS to the image registry and the endpoints in What needs outbound access; inbound 80/443 from your users; inbound reachability from the host to whatever you want to scan
OptionalA DNS name for the platform (TLS via Let's Encrypt or your own certificate); an SMTP relay; an OIDC identity provider; object storage (S3 / GCS / Azure / MinIO) for evidence and reports — local disk works

The stack

ServiceImageRole
frontendoffload-cspm-frontendNginx serving the UI and proxying /api — the only service exposed (80 / 443; 3000 loopback)
backendoffload-cspm-backendThe API (8001, loopback only)
celery-worker · celery-scan-worker · celery-code-worker · celery-long-workeroffload-cspm-backendBackground work by queue — cloud, Kubernetes and container scans; native web / API / network scans; code scans; long-running jobs. Each mounts the Docker socket to run scanner containers
celery-fix-workeroffload-cspm-fix-workerThe agentic fix pipeline (opt-in)
celery-beatoffload-cspm-backendThe scheduler: recurring scans, feeds, syncs, retention, reports
mongo (7) · redis (7)public imagesData store (loopback 27017) · cache, sessions, queue broker
certbotcertbot/certbotLet's Encrypt renewal when DOMAIN_NAME is set

Some scanner tools ship inside the backend image (nmap, Trivy, Syft, Grype); the rest run as sibling containers pulled from public registries on first use — OWASP ZAP, Nuclei, Prowler, testssl.sh — which is why the workers mount the Docker socket.

Install

docker login ghcr.io -u <username> # the read-only token from onboarding
cp .env.example .env # then edit — see the table below
mkdir -p data/mongo/db data/mongo/config data/mongo/backups
docker compose -f docker-compose.client.yml pull
docker compose -f docker-compose.client.yml up -d
docker compose -f docker-compose.client.yml ps # every service healthy

Then open http://<host>:3000 (or https://<DOMAIN_NAME>) and sign in as admin@offloadsecurity.com with DEFAULT_ADMIN_PASSWORD from .env (if you left it empty, the generated password is in the backend log). That account is the platform administrator; change its password immediately and enable MFA.

First-run setup wizard

The Platform Setup wizard walks through Service Connectivity (MongoDB, Redis) → Security KeysStoragePlatform Settings (name, public URL, environment) → Email / SMTPAdmin AccountComplete. It generates the encryption keys that protect stored credentials (CLOUD_ENCRYPTION_KEY, INTEGRATION_ENCRYPTION_KEY, SECRET_KEY, WEBHOOK_SECRET) and stores them in the platform's own configuration (platform_config.generated_secrets). Copy them into .env afterwards — .env.example carries the one-line extraction command — so a restore onto a fresh host can still read stored credentials. Any Admin can relaunch it later from the account menu; PLATFORM_SETUP_COMPLETE=true in .env locks it against reopening.

The .env values that matter on-premises

VariablePurpose
GHCR_OWNER · IMAGE_TAGWhich images to pull; IMAGE_TAG pins the release (latest by default)
REDIS_PASSWORD · SECRET_KEYRequired
DEFAULT_ADMIN_PASSWORDFirst-boot administrator password
PUBLIC_APP_URL · DOMAIN_NAMEThe URL users (and SSO redirects, emails) use; setting DOMAIN_NAME switches Nginx to HTTPS with Let's Encrypt
ALLOW_PRIVATE_SCAN_TARGETS=trueLet scans and integrations reach RFC-1918 addresses. Loopback and link-local / cloud-metadata stay blocked regardless. Read by the backend and the workers
SMTP_HOST · SMTP_PORT · SMTP_USER · SMTP_PASSWORD · SMTP_FROMPlatform mail (invitations, resets, notifications); overrides anything teams configure
OIDC_*Single sign-on — see Signing In & Sessions
ALLOW_PUBLIC_REGISTRATION=falseKeep it false; accounts come from invitations or SSO
MONGO_MEM_LIMIT · MONGO_WT_CACHE_GBSize MongoDB to the host (6 GB / 3 GB by default)
*_RETENTION_DAYSSCAN_RETENTION_DAYS 90 · CLOUD_SCAN_RETENTION_DAYS 180 · CONTAINER_SCAN_RETENTION_DAYS 180 · REPORT_RETENTION_DAYS 180 · AUDIT_TRAIL_RETENTION_DAYS 365
REMEDIATION_LIVE_EXECUTELeave false until you want the Auto-Fix Engine to change cloud resources
INTEGRATION_VERIFY_SSL · SMTP_CA_CERTPrivate-CA handling for integrations and mail

TLS

Set DOMAIN_NAME (and PUBLIC_APP_URL=https://…) and the front end serves 443 with a Let's Encrypt certificate renewed by the certbot service — which needs port 80 reachable from the internet for the challenge. Without DOMAIN_NAME the front end serves plain HTTP on port 3000 (loopback) and 80; air-gapped or internal-only installs terminate TLS on their own reverse proxy or load balancer in front of it.

Upgrades

Releases are image tags. To move:

# in .env: IMAGE_TAG=<new tag>
docker compose -f docker-compose.client.yml pull
docker compose -f docker-compose.client.yml up -d

Take a backup first. Expect users to sign in again after the restart; scans that were running are re-queued or reaped by the fleet-health sweeps. Release notes for the tag list anything that needs an operator's hand.

Backups

MongoDB is the system of record; evidence and report files live in the configured object storage (or the data/ volumes when local).

docker compose -f docker-compose.client.yml exec -T mongo /usr/local/bin/backup.sh
# lists / restores:
docker compose -f docker-compose.client.yml exec mongo ls /backups/
docker compose -f docker-compose.client.yml exec mongo /usr/local/bin/restore.sh cspm_backup_<timestamp>

backup.sh writes a timestamped dump to data/mongo/backups and keeps the last 7 daily and 4 weekly; schedule it from the host's crontab (the script header has the line). restore.sh takes a safety backup before replacing data. Back up .env and the wizard-generated keys with the dumps — encrypted credentials are unreadable without them.

What needs outbound access

DestinationWhyIf blocked
ghcr.io, Docker HubPlatform images; scanner images (ZAP, Nuclei, Prowler, testssl.sh) on first usePre-pull or mirror to an internal registry and point GHCR_OWNER / image references at it
Trivy and Grype vulnerability databases, Nuclei templatesContainer, dependency and template-based scans stay currentScans run on the bundled databases and age; mirror updates
The nine threat feeds (cisa.gov, abuse.ch, otx.alienvault.com, PhishTank, Blocklist.de, Spamhaus, OpenPhish)Threat intelligence, KEV-based prioritisationFeeds show degraded; import indicators via STIX instead
Cloud provider APIs (AWS, Azure, GCP)Cloud posture scans, asset discoveryThose modules cannot run
Your SCM (GitHub, GitLab, Bitbucket, Azure DevOps)Code scans, fix PRsUse an internal SCM the host can reach
LLM provider (Anthropic, OpenAI, Google) — optionalAI features; Knowledge Base embeddingsEverything works in rule-based mode
Let's Encrypt — optionalAutomatic TLSBring your own certificate / proxy
Your SMTP relay, Slack / Teams / PagerDuty / Jira endpointsNotifications and integrationsConfigure what you have

Nothing about your findings is sent to Offload Security. Support works from logs and exports you choose to share.

Health

GET /api/health is the liveness endpoint the front end and load balancers use; docker compose ps shows per-service health checks. The bundled monitoring (Prometheus, Grafana, Alertmanager, Loki) in the development compose file is optional on client installs; the platform's own scheduler and fleet-health sweeps recover stuck scans without it.