This guide provides production-ready Docker Compose configurations for deploying Vigil with security hardening, container isolation, and TLS termination. Suitable for Linux DevOps teams deploying status pages in production environments.
Latest Stable Version: v1.28.6 (November 28, 2025)
Docker Image: valeriansaliou/vigil:v1.28.6
Image Size: 9 MB (Alpine-based)
Architectures: x86_64, aarch64 (ARM64)
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 1 core | 2+ cores |
| RAM | 512 MB | 1 GB+ |
| Disk | 500 MB | 5 GB+ |
| Docker | 20.10+ | 24.0+ |
| Docker Compose | 2.0+ | 2.20+ |
# Install Docker (Debian/Ubuntu)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Install Docker Compose Plugin
sudo apt-get install -y docker-compose-plugin
# Verify installation
docker --version
docker compose version
docker run -d \
--name vigil \
--restart unless-stopped \
-p 8080:8080 \
-v ./config.cfg:/etc/vigil.cfg:ro \
-e VIGIL_MANAGER_TOKEN="your-manager-token" \
-e VIGIL_REPORTER_TOKEN="your-reporter-token" \
valeriansaliou/vigil:v1.28.6
docker ps | grep vigil
curl http://localhost:8080/status/report
/opt/vigil/
├── docker-compose.yml
├── .env
├── config/
│ └── config.cfg
├── data/
├── nginx/
│ ├── nginx.conf
│ ├── ssl/
│ │ ├── fullchain.pem
│ │ └── privkey.pem
│ └── html/
│ └── index.html
└── logs/
sudo mkdir -p /opt/vigil/{config,data,nginx/{ssl,html},logs}
sudo chown -R 1000:1000 /opt/vigil
sudo chmod 750 /opt/vigil
Create /opt/vigil/.env:
# Vigil Tokens (generate with: openssl rand -hex 32)
VIGIL_MANAGER_TOKEN=your-manager-token-min-32-chars
VIGIL_REPORTER_TOKEN=your-reporter-token-min-32-chars
# SMTP Credentials (if using email notifications)
SMTP_PASSWORD=your-smtp-password
# Database (if using external storage)
DB_PASSWORD=your-database-password
# Docker Compose Project Name
COMPOSE_PROJECT_NAME=vigil
Create /opt/vigil/docker-compose.yml:
services:
vigil:
image: valeriansaliou/vigil:v1.28.6
container_name: vigil
restart: unless-stopped
expose:
- "8080"
volumes:
- ./config/config.cfg:/etc/vigil.cfg:ro
- ./data:/var/lib/vigil:rw
env_file:
- .env
environment:
- TZ=UTC
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
- NET_RAW
network_mode: bridge
dns:
- 1.1.1.1
- 8.8.8.8
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/status/report"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- vigil-internal
nginx:
image: nginx:alpine
container_name: vigil-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- ./nginx/html:/usr/share/nginx/html:ro
- ./logs/nginx:/var/log/nginx:rw
depends_on:
vigil:
condition: service_healthy
read_only: true
tmpfs:
- /var/cache/nginx:rw,noexec,nosuid,size=64m,mode=1777
- /var/run:rw,noexec,nosuid,size=32m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
- CHOWN
- SETGID
- SETUID
network_mode: bridge
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
compress: "true"
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 30s
timeout: 10s
retries: 3
networks:
- vigil-internal
networks:
vigil-internal:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
The production compose file includes:
| Feature | Description | Benefit |
|---|---|---|
| read_only: true | Read-only root filesystem | Prevents runtime modifications |
| tmpfs mounts | In-memory temporary storage | No persistent writes to disk |
| cap_drop: ALL | Drop all Linux capabilities | Minimizes attack surface |
| cap_add | Add only required capabilities | Least privilege principle |
| no-new-privileges | Prevent privilege escalation | Blocks setuid/setgid exploits |
| network isolation | Internal Docker network | Isolates from host network |
| DNS configuration | Explicit DNS servers | Prevents DNS leaks |
| log rotation | Size-limited log files | Prevents disk exhaustion |
For enhanced security, add to vigil service:
services:
vigil:
# ... existing config ...
# User namespace remapping (requires Docker daemon config)
user: "1000:1000"
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
# PIDs limit
pids_limit: 100
# Stop grace period
stop_grace_period: 30s
For production, configure /etc/docker/daemon.json:
{
"userns-remap": "default",
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"userland-proxy": false,
"no-new-privileges": true
}
Restart Docker after changes:
sudo systemctl restart docker
Create /opt/vigil/nginx/nginx.conf:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 10M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/xml application/xml+rss text/javascript application/x-javascript;
# Security headers
map $sent_http_content_type $cors_header {
default $sent_http_content_type;
"application/json" "application/json";
}
# Rate limiting zone
limit_req_zone $binary_remote_addr zone=vigil_limit:10m rate=10r/s;
# Upstream backend
upstream vigil_backend {
server vigil:8080;
keepalive 32;
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name status.example.com;
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
}
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name status.example.com;
# SSL certificates
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# TLS configuration (TLS 1.3 only)
ssl_protocols TLSv1.3;
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/fullchain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Rate limiting
limit_req zone=vigil_limit burst=20 nodelay;
# Proxy settings
location / {
proxy_pass http://vigil_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Deny access to sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
}
cd /opt/vigil/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
-keyout privkey.pem \
-out fullchain.pem \
-subj "/C=US/ST=State/L=City/O=Organization/CN=status.example.com" \
-addext "subjectAltName=DNS:status.example.com"
chmod 600 privkey.pem
chmod 644 fullchain.pem
# Install Certbot
sudo apt-get install -y certbot
# Obtain certificate
sudo certbot certonly --standalone \
-d status.example.com \
--email admin@example.com \
--agree-tos \
--non-interactive
# Copy certificates
sudo cp /etc/letsencrypt/live/status.example.com/fullchain.pem /opt/vigil/nginx/ssl/
sudo cp /etc/letsencrypt/live/status.example.com/privkey.pem /opt/vigil/nginx/ssl/
sudo chmod 644 /opt/vigil/nginx/ssl/fullchain.pem
sudo chmod 600 /opt/vigil/nginx/ssl/privkey.pem
| Variable | Description | Required | Default |
|---|---|---|---|
VIGIL_MANAGER_TOKEN |
Manager API authentication token | Yes | - |
VIGIL_REPORTER_TOKEN |
Reporter API authentication token | Yes | - |
SMTP_PASSWORD |
SMTP server password for email notifications | No | - |
DB_PASSWORD |
Database password (if applicable) | No | - |
TZ |
Timezone for logs and timestamps | No | UTC |
# Generate 32-character hex token
openssl rand -hex 32
# Generate URL-safe base64 token
openssl rand -base64 32 | tr -d '\n'
| Volume | Purpose | Permissions |
|---|---|---|
./config/config.cfg |
Vigil configuration file | Read-only |
./data |
Vigil state and data | Read-write |
./nginx/ssl |
TLS certificates | Read-only |
./logs |
Nginx access/error logs | Read-write |
# Create backup directory
mkdir -p /backup/vigil
# Backup configuration and data
tar -czvf /backup/vigil/config-$(date +%Y%m%d).tar.gz \
/opt/vigil/config /opt/vigil/data
# Verify backup
tar -tzf /backup/vigil/config-$(date +%Y%m%d).tar.gz
# Stop containers
docker compose down
# Restore files
tar -xzvf /backup/vigil/config-20260216.tar.gz -C /opt/vigil/
# Start containers
docker compose up -d
# All logs
docker compose logs -f
# Vigil only
docker compose logs -f vigil
# Nginx only
docker compose logs -f nginx
# Last 100 lines
docker compose logs --tail=100 vigil
Docker Compose file includes automatic log rotation:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
logging:
driver: syslog
options:
syslog-address: "udp://127.0.0.1:514"
tag: "vigil"
# Check container health
docker inspect --format='{{.State.Health.Status}}' vigil
# Test health endpoint
curl http://localhost:8080/status/report
# Monitor with watch
watch -n 5 'docker inspect --format="{{.State.Health.Status}}" vigil'
Create /opt/vigil/backup.sh:
#!/bin/bash
BACKUP_DIR="/backup/vigil"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
mkdir -p "$BACKUP_DIR"
# Backup configuration
tar -czvf "$BACKUP_DIR/config-$DATE.tar.gz" \
/opt/vigil/config \
/opt/vigil/data \
/opt/vigil/.env \
/opt/vigil/docker-compose.yml
# Backup nginx SSL
tar -czvf "$BACKUP_DIR/ssl-$DATE.tar.gz" \
/opt/vigil/nginx/ssl
# Remove old backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $DATE"
Make executable and schedule with cron:
chmod +x /opt/vigil/backup.sh
# Add to crontab (daily at 2 AM)
echo "0 2 * * * /opt/vigil/backup.sh" | sudo crontab -
Restore configuration:
tar -xzvf /backup/vigil/config-20260216.tar.gz -C /opt/vigil/
Restore SSL certificates:
tar -xzvf /backup/vigil/ssl-20260216.tar.gz -C /opt/vigil/
Start containers:
docker compose up -d
Verify health:
docker compose ps
curl https://status.example.com/status/report
docker inspect valeriansaliou/vigil:v1.28.6 --format='{{index .Config.Labels "org.opencontainers.image.version"}}'
cd /opt/vigil
# 1. Backup current state
docker compose down
tar -czvf /backup/vigil/pre-upgrade-$(date +%Y%m%d).tar.gz \
config data .env
# 2. Pull new image
docker compose pull
# 3. Review changes (check GitHub releases)
# https://github.com/valeriansaliou/vigil/releases
# 4. Update image tag in docker-compose.yml if needed
sed -i 's/vigil:v1.28.6/vigil:v1.29.0/' docker-compose.yml
# 5. Start new version
docker compose up -d
# 6. Verify health
docker compose ps
curl http://localhost:8080/status/report
docker compose logs --tail=50 vigil
cd /opt/vigil
# Stop current version
docker compose down
# Restore backup
tar -xzvf /backup/vigil/pre-upgrade-20260216.tar.gz -C /opt/vigil/
# Start previous version
docker compose up -d
# Check logs
docker compose logs vigil
# Validate configuration
docker run --rm -v ./config/config.cfg:/etc/vigil.cfg:ro \
valeriansaliou/vigil:v1.28.6 vigil -c /etc/vigil.cfg --check
# Check file permissions
ls -la /opt/vigil/config/config.cfg
# Test endpoint manually
curl -v http://localhost:8080/status/report
# Check container network
docker network inspect vigil_vigil-internal
# Verify DNS resolution
docker exec vigil nslookup api.example.com
# Check memory limits
docker stats vigil
# Adjust resource limits in docker-compose.yml
# See "Additional Security Options" section
# Verify certificate
openssl x509 -in /opt/vigil/nginx/ssl/fullchain.pem -text -noout
# Check certificate expiry
openssl x509 -enddate -noout -in /opt/vigil/nginx/ssl/fullchain.pem
# Test SSL connection
openssl s_client -connect status.example.com:443 -servername status.example.com
# Test from inside container
docker exec vigil wget -q -O- http://localhost:8080/status/report
# Check firewall rules
sudo iptables -L -n | grep vigil
# Verify port binding
sudo netstat -tlnp | grep 8080
Any questions?
Feel free to contact us. Find all contact information on our contact page.