Contigov – Deployment Guide
This guide covers everything you need to deploy the Contigov server, connect agents, and go live in production. Follow the sections in order for the smoothest experience.
Table of Contents
- Architecture Overview
- Prerequisites
- Generate Required Secrets
- Configure the .env File
- Deployment Options
- First Login & Initial Setup
- Deploying Agents
- Health & Readiness Checks
- Database Backups
- Upgrading
- Troubleshooting
1. Architecture Overview
Contigov consists of two components:
- Central Server — a Node.js/Express application with an AES-256-GCM encrypted SQLite database, served as a Docker container. It provides the web UI, REST API, policy engine, and audit log.
- Agent — a compiled Go binary that runs natively on each managed endpoint (Windows, macOS, Linux). It reports container and image data, enforces policy via the system hosts file, intercepts blocked pull attempts, and runs Trivy vulnerability scans.
Agents communicate with the server over HTTPS (or HTTP in proxy mode behind a load balancer). The server supports up to 1,000 simultaneous agents on a single instance.
2. Prerequisites
Server host
- Docker Engine 20.10+ (or Docker Desktop)
- 1 GB RAM minimum (2 GB recommended for fleets > 100 agents)
- 10 GB disk (database grows with fleet size and scan results)
- Ports 443 and 80 open for direct TLS, or 3443 open to your load balancer for proxy mode
Managed endpoints (agents)
- Docker or Podman installed and running
- Network access to the Contigov server URL
- Administrator / root privileges (for hosts file enforcement)
3. Generate Required Secrets
Three secrets must be created before starting the server. Run these commands on any machine that has OpenSSL installed.
On Linux / macOS
openssl rand -hex 32
# Database encryption key — AES-256-GCM, encrypts the SQLite file at rest
openssl rand -hex 32
# Agent API key — shared secret between server and all agents
openssl rand -hex 16
On Windows (PowerShell)
[System.BitConverter]::ToString(
(1..32 | % { [byte](Get-Random -Max 256) })
).Replace(‘-‘,”).ToLower()
⚠ Important: Save all three values securely (password manager, AWS Secrets Manager, etc.). The database encryption key cannot be recovered — losing it means losing all stored data.
4. Configure the .env File
All runtime configuration is provided through a single .env file. Create this file on your server host before starting the container.
# Signs all JWT session tokens. Keep secret and consistent across restarts.
JWT_SECRET=your-64-char-hex-value-here
# AES-256-GCM key for encrypting the SQLite database at rest.
# WARNING: losing this key means losing all data — back it up safely.
DB_ENCRYPTION_KEY=your-64-char-hex-value-here
# Shared secret that all agents must send in the Authorization header.
# Set the same value as DOCKER_GOV_API_KEY in each agent’s config.
API_KEY=your-32-char-hex-value-here
# Enforce production mode (required secrets, secure cookies)
NODE_ENV=production
# ── Recommended ───────────────────────────────────────────────────────────────
# Comma-separated list of allowed browser origins (CORS)
ALLOWED_ORIGINS=https://your-domain.com
# Externally reachable URL of this server — written into agent config downloads
PUBLIC_SERVER_URL=https://your-domain.com
# ── TLS Mode ──────────────────────────────────────────────────────────────────
# auto = serve HTTPS directly using the built-in self-signed cert (default)
# proxy = TLS terminated by upstream load balancer (AWS ALB + ACM, nginx)
# direct = HTTPS only using your own certificate files; exits if certs missing
TLS_MODE=auto
# ── Optional ──────────────────────────────────────────────────────────────────
# Path for hourly encrypted database backups inside the container
# Mount a separate Docker volume at this path
# BACKUP_DIR=/backups
Environment variable reference
5. Deployment Options
Option A — Self-Signed Certificate (Quick Start / Internal Networks)
Best for: internal networks, evaluation, or any environment where you control what browsers connect.
The Docker image ships with a self-signed certificate baked in. Zero configuration required for TLS — just start the container.
Step 1 — Pull the image
Step 2 — Create the data volume
Step 3 — Start the container
–name contigov-server \
–restart unless-stopped \
-p 443:3443 \
-p 80:3080 \
-v contigov-data:/app/data \
–env-file .env \
reg.lami.co.il/contigov:latest
Step 4 — Verify it is running
# You should see: Contigov server vX.X.X starting
Access the UI at https://your-server-ip. Accept the browser’s self-signed certificate warning on first visit.
Port 80 automatically redirects to HTTPS — no separate configuration needed.
Option B — AWS EC2 + Application Load Balancer + ACM (Recommended for AWS)
Best for: production deployments on AWS where you want a trusted public certificate managed by ACM.
In this mode the ALB handles TLS termination. The container runs plain HTTP internally and trusts X-Forwarded headers from the ALB.
Step 1 — Set TLS_MODE in your .env
NODE_ENV=production
ALLOWED_ORIGINS=https://your-domain.com
PUBLIC_SERVER_URL=https://your-domain.com
Step 2 — Start the container (HTTP only, no port 80 needed)
–name contigov-server \
–restart unless-stopped \
-p 3443:3443 \
-v contigov-data:/app/data \
–env-file .env \
reg.lami.co.il/contigov:latest
Step 3 — Create an EC2 Security Group for the instance
- Inbound: TCP 3443 from ALB security group ID
- Inbound: TCP 22 from your IP (for SSH management)
- Outbound: all traffic (agents and Trivy downloads)
Step 4 — Create an ALB Security Group
- Inbound: TCP 443 from your allowed IP range (or your corporate security group)
- Inbound: TCP 80 from same range (for HTTP→HTTPS redirect)
- Outbound: TCP 3443 to the EC2 instance security group
Step 5 — Create a Target Group
- Target type: Instance
- Protocol: HTTP
- Port: 3443
- Protocol version: HTTP1 (not HTTP2)
- Health check path:
/healthz - Register your EC2 instance as a target
Step 6 — Create the Application Load Balancer
- Scheme: Internet-facing (or Internal if only accessed via VPN)
- Subnets: select public subnets (subnets with an Internet Gateway route) for internet-facing
- Security group: the ALB security group from Step 4
Step 7 — Add ALB Listeners
- HTTPS:443 → Forward to your target group. Attach your ACM certificate here.
- HTTP:80 → Redirect to HTTPS:443 (status 301). Do not forward to the target group.
Step 8 — Create a DNS record
In Route 53 (or your DNS provider), create an A record (Alias) for your domain pointing to the ALB DNS name. If using a third-party DNS provider, create a CNAME record instead.
Step 9 — Verify
curl http://localhost:3443/healthz
# From your browser — should load the login page with a trusted certificate
https://your-domain.com
Option C — Custom / Wildcard Certificate (Direct TLS on the Container)
Best for: deployments where you have your own certificate (Sectigo, DigiCert, Let’s Encrypt, wildcard) and want the container to terminate TLS directly without a load balancer.
Step 1 — Prepare certificate files
You need two files:
server.crt— your certificate plus the full chain (intermediate and root CAs concatenated in order)server.key— the private key
For a Sectigo / commercial wildcard certificate, concatenate in this order:
Step 2 — Upload certificate files to the host
sudo cp server.crt /opt/contigov/certs/server.crt
sudo cp server.key /opt/contigov/certs/server.key
sudo chmod 600 /opt/contigov/certs/server.key
Step 3 — Set TLS variables in .env
TLS_CERT=/certs/server.crt
TLS_KEY=/certs/server.key
NODE_ENV=production
ALLOWED_ORIGINS=https://your-domain.com
PUBLIC_SERVER_URL=https://your-domain.com
Step 4 — Start the container with the cert volume mounted
–name contigov-server \
–restart unless-stopped \
-p 443:3443 \
-p 80:3080 \
-v contigov-data:/app/data \
-v /opt/contigov/certs:/certs:ro \
–env-file .env \
reg.lami.co.il/contigov:latest
Step 5 — Verify
# Certificate subject should match your domain
# Response: {“status”:”ok”}
6. First Login & Initial Setup
Default credentials
Change the password immediately after first login: Settings → Users → Change My Password.
Recommended first-login checklist
- Change the admin password — Settings → Users → Change My Password
- Verify the server version — shown in the top-left corner of the dashboard
- Set your CORS origin — Settings → Security → Allowed Origins (if not already set in .env)
- Enable MFA — Settings → Users → Two-Factor Authentication for the admin account
- Set the Public Server URL — Settings → General (required for agent config downloads to work correctly)
- Download the agent config — Settings → Agent Deploy → Download agent-config.json
- Deploy at least one agent — see Section 7 below
7. Deploying Agents
Download the agent
- Open the web UI → Settings → Agent Deploy
- Click Download agent-config.json — pre-filled with your server URL and API key
- Download the binary for your target operating system:
Place agent-config.json in the same folder as the binary. The agent picks it up automatically — no environment variables needed.
Windows — Run as a Service (Manual)
The agent self-elevates via UAC when run interactively, or can be installed as a Windows Service that starts automatically at boot with no user interaction.
Interactive (one-time / testing)
A UAC prompt will appear once to allow hosts file modifications.
Windows Service (recommended for production)
Open PowerShell as Administrator:
New-Item -ItemType Directory -Force -Path C:\ProgramData\contigov
Copy-Item agent-windows-amd64.exe C:\ProgramData\contigov\agent.exe
Copy-Item agent-config.json C:\ProgramData\contigov\agent-config.json
# Install and start the service
C:\ProgramData\contigov\agent.exe –install-service
sc start ContigovAgent
The service runs as LocalSystem, survives reboots, and requires no user to be logged in.
Remove the service
C:\ProgramData\contigov\agent.exe –uninstall-service
Windows — Mass Deployment via Microsoft Intune
Use this method to deploy the Contigov agent silently to all Windows devices managed by Microsoft Intune (Endpoint Manager) — no user interaction or manual steps on each device.
What you need before starting
- Microsoft Intune license (Microsoft 365 Business Premium, E3, or E5)
- The
agent-windows-amd64.exebinary - The
agent-config.jsonfile downloaded from Settings → Agent Deploy - The Microsoft Win32 Content Prep Tool (
IntuneWinAppUtil.exe) — download free from GitHub
Step 1 — Prepare the installer folder
On your admin workstation, create a folder with the following files:
agent.exe ← renamed from agent-windows-amd64.exe
agent-config.json ← downloaded from Settings → Agent Deploy
install.ps1 ← PowerShell install script (see below)
uninstall.ps1 ← PowerShell uninstall script (see below)
Create install.ps1:
$dest = “C:\ProgramData\contigov”
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item “$PSScriptRoot\agent.exe” “$dest\agent.exe” -Force
Copy-Item “$PSScriptRoot\agent-config.json” “$dest\agent-config.json” -Force
# Install and start as a Windows Service
Start-Process -FilePath “$dest\agent.exe” -ArgumentList “–install-service” -Wait -Verb RunAs
Start-Service -Name ContigovAgent
Create uninstall.ps1:
Stop-Service -Name ContigovAgent -ErrorAction SilentlyContinue
Start-Process -FilePath “C:\ProgramData\contigov\agent.exe” -ArgumentList “–uninstall-service” -Wait
Remove-Item -Path “C:\ProgramData\contigov” -Recurse -Force -ErrorAction SilentlyContinue
Step 2 — Package as an .intunewin file
Open Command Prompt on your admin workstation and run:
-c C:\ContigovPackage ^
-s install.ps1 ^
-o C:\ContigovOutput
This produces C:\ContigovOutput\install.intunewin — the file you upload to Intune.
Step 3 — Add the app in Intune
- Open intune.microsoft.com and sign in as an Intune Administrator
- Navigate to Apps → Windows → Add
- Select app type: Windows app (Win32) → Click Select
- Under App information, click Select app package file and upload
install.intunewin - Fill in the app details:
- Name: Contigov Agent
- Description: Docker governance and container policy enforcement agent
- Publisher: Contigov
- Version: (paste the version shown in your UI, e.g. 1.0.42)
- Click Next
Step 4 — Configure install and uninstall commands
Click Next.
Step 5 — Set detection rules
Detection rules tell Intune whether the app is already installed. Choose Manually configure detection rules and add:
Click Next.
Step 6 — Assign to devices
- Under Assignments, click Add group under Required
- Select the Azure AD group that contains your target Windows devices (or select All Devices to deploy to all enrolled Windows machines)
- Click Next → Create
Step 7 — Monitor deployment
- Go to Apps → Windows → Contigov Agent
- Click Device install status to see per-device results
- Intune deploys silently within the next sync cycle (typically 15–60 minutes). You can force a sync from the device: Settings → Accounts → Access work or school → Info → Sync
Once deployed, the agent appears as a service named ContigovAgent in services.msc and the endpoint shows up in the Contigov dashboard within one report cycle (default 30 seconds after first start).
Linux — systemd Service
Step 1 — Copy files
sudo cp agent-linux-amd64 /opt/contigov/agent
sudo cp agent-config.json /opt/contigov/agent-config.json
sudo chmod +x /opt/contigov/agent
Step 2 — Create the systemd unit file
Create /etc/systemd/system/contigov-agent.service:
Description=Contigov Agent
After=network.target docker.service
[Service]
ExecStart=/opt/contigov/agent
WorkingDirectory=/opt/contigov
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Step 3 — Enable and start
sudo systemctl enable –now contigov-agent
# Check status
sudo systemctl status contigov-agent
# View live logs
journalctl -u contigov-agent -f
Stop / remove
sudo rm /etc/systemd/system/contigov-agent.service
macOS — launchd Daemon (Manual)
The launchd daemon runs as root at boot, before any user logs in — no user interaction required.
Step 1 — Copy files
sudo cp agent-darwin-arm64 /opt/contigov/agent # or agent-darwin-amd64 for Intel
sudo cp agent-config.json /opt/contigov/agent-config.json
sudo chmod +x /opt/contigov/agent
Step 2 — Remove Gatekeeper quarantine
Step 3 — Create the launchd plist
Create /Library/LaunchDaemons/com.contigov.agent.plist:
<!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN”
“http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
<plist version=”1.0″>
<dict>
<key>Label</key> <string>com.contigov.agent</string>
<key>ProgramArguments</key>
<array><string>/opt/contigov/agent</string></array>
<key>WorkingDirectory</key> <string>/opt/contigov</string>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>/var/log/contigov-agent.log</string>
<key>StandardErrorPath</key> <string>/var/log/contigov-agent.log</string>
</dict>
</plist>
Step 4 — Load and verify
# Verify it is running (should show a PID)
sudo launchctl list | grep contigov
# View live logs
tail -f /var/log/contigov-agent.log
Stop / remove
sudo rm /Library/LaunchDaemons/com.contigov.agent.plist
macOS — Mass Deployment via Jamf Pro
Use this method to silently deploy the Contigov agent to all Mac devices enrolled in Jamf Pro — zero touch from the end user.
What you need before starting
- Jamf Pro instance with admin access
- The
agent-darwin-arm64and/oragent-darwin-amd64binaries - The
agent-config.jsondownloaded from Settings → Agent Deploy - Packages (pkg) tool — use Packages.app (free) or
pkgbuild(built into macOS) to create a.pkginstaller
Step 1 — Create the install script
Create a file named postinstall (no extension). This runs automatically after the package is installed:
# postinstall — runs as root on each managed Mac
# Remove Gatekeeper quarantine (binary is not notarized)
xattr -rd com.apple.quarantine /opt/contigov/agent 2>/dev/null || true
# Set correct permissions
chmod +x /opt/contigov/agent
chmod 600 /opt/contigov/agent-config.json
# Remove any existing launchd service before loading fresh
launchctl unload /Library/LaunchDaemons/com.contigov.agent.plist 2>/dev/null || true
# Load and enable the daemon
launchctl load -w /Library/LaunchDaemons/com.contigov.agent.plist
exit 0
Make it executable:
Step 2 — Build the .pkg installer
Arrange files in a staging folder that mirrors the target filesystem:
opt/
contigov/
agent ← binary (arm64, amd64, or universal)
agent-config.json ← your pre-filled config
Library/
LaunchDaemons/
com.contigov.agent.plist ← the plist from Step 3 above
Build the package using pkgbuild (run from your Mac terminal):
–root ./pkg-root \
–scripts ./scripts \
–identifier com.contigov.agent \
–version 1.0.0 \
–install-location / \
ContigovAgent.pkg
The --scripts folder should contain your postinstall file.
Step 3 — Upload the package to Jamf Pro
- Log in to your Jamf Pro instance
- Go to Settings → Computer Management → Packages
- Click New and upload
ContigovAgent.pkg - Set the display name to Contigov Agent and click Save
Step 4 — Create an Uninstall Script (optional but recommended)
- Go to Settings → Computer Management → Scripts
- Click New, name it Uninstall Contigov Agent, set Priority to After
- Paste the following into the Script field:
launchctl unload /Library/LaunchDaemons/com.contigov.agent.plist 2>/dev/null
rm -f /Library/LaunchDaemons/com.contigov.agent.plist
rm -rf /opt/contigov
exit 0
- Click Save
Step 5 — Create a Jamf Policy
- Go to Computers → Policies → New
- Under General:
- Display Name: Deploy Contigov Agent
- Trigger: Enrollment Complete, Recurring Check-in (or both)
- Execution Frequency: Once per computer
- Under Packages, click Configure → Add ContigovAgent.pkg → Action: Install
- Under Scope, choose your target computers or smart group (e.g. All Managed Clients, or a specific department group)
- Click Save
Step 6 — Force an immediate check-in (optional)
To deploy without waiting for the next scheduled check-in, use Jamf Remote or run from a managed device:
Step 7 — Verify deployment
In Jamf Pro, go to Computers → Policies → Deploy Contigov Agent → Logs to see per-device install results.
On any deployed Mac, verify the agent is running:
# Expected output: a line with a PID and com.contigov.agent
tail -20 /var/log/contigov-agent.log
# Expected: agent startup messages and first report to the server
The device should appear in the Contigov dashboard within 30 seconds of the agent starting for the first time.
Updating the agent via Jamf
- Download the new binary from Settings → Agent Deploy (the
agent-config.jsondoes not change unless your server URL or API key changes) - Build a new
.pkgwith an incremented version number - Upload the new package to Jamf and update the policy to reference it
- Change the policy Execution Frequency to Once per computer per version so it reinstalls only on devices that have an older version
8. Health & Readiness Checks
Two HTTP endpoints are available for monitoring and load balancer health checks:
curl https://your-domain.com/healthz
# Expected: {“status”:”ok”}
curl https://your-domain.com/readyz
# Expected: {“status”:”ready”}
9. Database Backups
The server can create automatic hourly encrypted backups of the database. Backups are AES-256-GCM encrypted (using the same DB_ENCRYPTION_KEY) and automatically pruned after 7 days.
Enable backups
Add BACKUP_DIR=/backups to your .env file and mount a dedicated volume:
docker run -d \
–name contigov-server \
–restart unless-stopped \
-p 443:3443 \
-p 80:3080 \
-v contigov-data:/app/data \
-v contigov-backups:/backups \
–env-file .env \
reg.lami.co.il/contigov:latest
Restore from backup
docker stop contigov-server
# Decrypt a backup file
openssl enc -d -aes-256-gcm \
-K $DB_ENCRYPTION_KEY \
-in /path/to/backup.enc \
-out governance.db
# Replace the active database
docker cp governance.db contigov-server:/app/data/governance.db
# Restart
docker start contigov-server
10. Upgrading
Upgrades are non-destructive. The database schema is migrated automatically on startup. Always back up your data volume before upgrading.
Pull the new image and restart
docker pull reg.lami.co.il/contigov:latest
# Stop and remove the old container (data volume is preserved)
docker stop contigov-server
docker rm contigov-server
# Start a new container with the same flags as before
docker run -d \
–name contigov-server \
–restart unless-stopped \
-p 443:3443 \
-p 80:3080 \
-v contigov-data:/app/data \
–env-file .env \
reg.lami.co.il/contigov:latest
# Verify the new version
docker logs contigov-server | head -5
After upgrading the server, download a fresh agent-config.json from Settings → Agent Deploy and redeploy the agent binary to all endpoints to ensure version alignment.
11. Troubleshooting
Container exits immediately on startup
Check the logs for a fatal startup error:
Common causes:
JWT_SECRETorDB_ENCRYPTION_KEYnot set andNODE_ENV=productionis active — generate and add the secrets to.envTLS_MODE=directbut cert files are not mounted or paths are wrong — verifyTLS_CERTandTLS_KEYpaths and the volume mount
Browser shows “Not Secure” or cert warning
- Self-signed cert: expected — click Advanced → Proceed. For production, use Option B (ALB+ACM) or Option C (custom cert).
- Wrong certificate loaded: verify with
curl -v https://your-domain.com/healthzand check the certificate subject in the output - Chrome HSTS cache: visit
chrome://net-internals/#hsts, search for your domain, and click Delete
ALB Target Group shows Unhealthy
- Confirm the EC2 security group allows inbound TCP 3443 from the ALB security group (not from 0.0.0.0/0)
- Confirm the Target Group protocol version is set to HTTP1 (not HTTP2)
- Confirm the container is running:
docker psandcurl http://localhost:3443/healthzfrom the EC2 instance - Confirm
TLS_MODE=proxyis set in.env— the ALB sends plain HTTP to port 3443, not HTTPS
Agents not appearing in the dashboard
- Verify the agent can reach the server:
curl https://your-domain.com/healthzfrom the endpoint - Verify
agent-config.jsoncontains the correct server URL and API key - Check the agent log for authentication errors (401) — the API key in the agent config must exactly match
API_KEYin the server’s.env - On macOS, ensure the Gatekeeper quarantine flag was removed:
sudo xattr -rd com.apple.quarantine /opt/contigov/agent
.env file not being parsed correctly on Linux
If you created the .env file on Windows and transferred it to Linux, it may contain Windows line endings (\r\n) or a UTF-8 BOM that breaks Docker’s env file parser:
sed -i ‘s/\r//’ .env
# Strip UTF-8 BOM (if present)
sed -i ‘1s/^\xEF\xBB\xBF//’ .env