This guide provides production-ready Docker Compose configurations for Statping-ng with security hardening, database options, and TLS termination.
| Registry | Image | Tags | Architecture |
|---|---|---|---|
| Docker Hub | adamboutcher/statping-ng |
latest, 0.93.0, dev |
amd64, arm64, arm/v7 |
| Quay.io | quay.io/statping-ng/statping-ng |
latest, 0.93.0, dev |
amd64, arm64, arm/v7 |
β οΈ Image Note: The primary maintained image is
adamboutcher/statping-ng. Thelatesttag currently points to v0.93.0. For production, pin to a specific version tag (e.g.,0.93.0) to avoid unexpected updates.
For testing and evaluation only. Not recommended for production.
# docker-compose.yml
services:
statping-ng:
image: adamboutcher/statping-ng:0.93.0
container_name: statping-ng
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- statping_data:/app
environment:
- NAME=My Status Page
- DESCRIPTION=Service monitoring dashboard
- DISABLE_LOGS=false
volumes:
statping_data:
# Deploy
docker compose up -d
# View logs
docker compose logs -f statping-ng
# Access at http://localhost:8080
Full stack with PostgreSQL database, Nginx reverse proxy, and automatic SSL certificates.
mkdir -p ~/statping-ng/{config,data,nginx/{certs,vhost,dhparam,html}}
cd ~/statping-ng
# docker-compose.yml
version: '3.8'
services:
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Statping-ng Application
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
statping-ng:
image: adamboutcher/statping-ng:0.93.0
container_name: statping-ng
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
networks:
- frontend
- backend
volumes:
- statping_data:/app
environment:
# Application settings
- NAME=My Status Page
- DESCRIPTION=Service monitoring and uptime dashboard
- VIRTUAL_HOST=status.example.com
- VIRTUAL_PORT=8080
- LETSENCRYPT_HOST=status.example.com
- LETSENCRYPT_EMAIL=admin@example.com
# Database connection
- DB_CONN=postgres
- DB_HOST=postgres
- DB_PORT=5432
- DB_USER=statping
- DB_PASS=${DB_PASSWORD:-ChangeMe123!}
- DB_DATABASE=statping
# Optional: Disable logs for performance
- DISABLE_LOGS=false
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
# Security hardening
security_opt:
- no-new-privileges:true
read_only: false # Set true with tmpfs for /app if needed
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# PostgreSQL Database
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
postgres:
image: postgres:15-alpine
container_name: statping-postgres
restart: unless-stopped
networks:
- backend
volumes:
- postgres_data:/var/lib/postgresql/data
- ./config/postgres/initdb.d:/docker-entrypoint-initdb.d:ro
environment:
- POSTGRES_USER=statping
- POSTGRES_PASSWORD=${DB_PASSWORD:-ChangeMe123!}
- POSTGRES_DB=statping
- POSTGRES_INITDB_ARGS=--encoding=UTF8 --lc-collate=C --lc-ctype=C
healthcheck:
test: ["CMD-SHELL", "pg_isready -U statping -d statping"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.5'
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- DAC_OVERRIDE
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Nginx Reverse Proxy
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
nginx-proxy:
image: nginxproxy/nginx-proxy:1.4
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
networks:
- frontend
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- nginx_certs:/etc/nginx/certs:ro
- nginx_vhost:/etc/nginx/vhost.d
- nginx_html:/usr/share/nginx/html
- nginx_dhparam:/etc/nginx/dhparam:ro
environment:
- DEFAULT_HOST=status.example.com
labels:
- "com.github.nginx-proxy.nginx"
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- NET_BIND_SERVICE
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Let's Encrypt Companion (Automatic SSL)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
letsencrypt-companion:
image: nginxproxy/acme-companion:2.2
container_name: letsencrypt-companion
restart: unless-stopped
depends_on:
- nginx-proxy
networks:
- frontend
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- nginx_certs:/etc/nginx/certs:rw
- nginx_vhost:/etc/nginx/vhost.d
- nginx_html:/usr/share/nginx/html
- acme_data:/etc/acme.sh
environment:
- DEFAULT_EMAIL=admin@example.com
- NGINX_PROXY_CONTAINER=nginx-proxy
networks:
frontend:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
backend:
driver: bridge
internal: true # No external access
ipam:
config:
- subnet: 172.29.0.0/16
volumes:
statping_data:
driver: local
postgres_data:
driver: local
nginx_certs:
driver: local
nginx_vhost:
driver: local
nginx_html:
driver: local
nginx_dhparam:
driver: local
acme_data:
driver: local
# .env
DB_PASSWORD=YourSecurePassword123!
# Generate strong DH parameters (one-time, takes ~5-10 minutes)
openssl dhparam -out ./nginx/dhparam/dhparam.pem 4096
# Deploy the stack
docker compose up -d
# Verify all containers are running
docker compose ps
# View application logs
docker compose logs -f statping-ng
# View database logs
docker compose logs -f postgres
# View nginx logs
docker compose logs -f nginx-proxy
Alternative configuration using MariaDB instead of PostgreSQL.
# docker-compose.yml (MySQL/MariaDB variant)
version: '3.8'
services:
statping-ng:
image: adamboutcher/statping-ng:0.93.0
container_name: statping-ng
restart: unless-stopped
depends_on:
mariadb:
condition: service_healthy
networks:
- frontend
- backend
volumes:
- statping_data:/app
environment:
- NAME=My Status Page
- DESCRIPTION=Service monitoring dashboard
- VIRTUAL_HOST=status.example.com
- VIRTUAL_PORT=8080
- LETSENCRYPT_HOST=status.example.com
- LETSENCRYPT_EMAIL=admin@example.com
- DB_CONN=mysql
- DB_HOST=mariadb
- DB_PORT=3306
- DB_USER=statping
- DB_PASS=${DB_PASSWORD:-ChangeMe123!}
- DB_DATABASE=statping
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
mariadb:
image: mariadb:10.11
container_name: statping-mariadb
restart: unless-stopped
networks:
- backend
volumes:
- mariadb_data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${ROOT_PASSWORD:-RootSecure123!}
- MYSQL_USER=statping
- MYSQL_PASSWORD=${DB_PASSWORD:-ChangeMe123!}
- MYSQL_DATABASE=statping
- MYSQL_CHARSET=utf8mb4
- MYSQL_COLLATION=utf8mb4_unicode_ci
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
nginx-proxy:
image: nginxproxy/nginx-proxy:1.4
container_name: nginx-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
networks:
- frontend
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- nginx_certs:/etc/nginx/certs:ro
- nginx_vhost:/etc/nginx/vhost.d
- nginx_html:/usr/share/nginx/html
letsencrypt-companion:
image: nginxproxy/acme-companion:2.2
container_name: letsencrypt-companion
restart: unless-stopped
depends_on:
- nginx-proxy
networks:
- frontend
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- nginx_certs:/etc/nginx/certs:rw
- nginx_vhost:/etc/nginx/vhost.d
- nginx_html:/usr/share/nginx/html
- acme_data:/etc/acme.sh
environment:
- DEFAULT_EMAIL=admin@example.com
- NGINX_PROXY_CONTAINER=nginx-proxy
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
statping_data:
driver: local
mariadb_data:
driver: local
nginx_certs:
driver: local
nginx_vhost:
driver: local
nginx_html:
driver: local
acme_data:
driver: local
If you prefer manual certificate management instead of Letβs Encrypt companion.
# docker-compose.yml (Manual TLS)
version: '3.8'
services:
statping-ng:
image: adamboutcher/statping-ng:0.93.0
container_name: statping-ng
restart: unless-stopped
ports:
- "443:443"
volumes:
- statping_data:/app
- ./certs/server.crt:/app/server.crt:ro
- ./certs/server.key:/app/server.key:ro
environment:
- NAME=My Status Page
- DESCRIPTION=Service monitoring dashboard
- DB_CONN=postgres
- DB_HOST=postgres
- DB_USER=statping
- DB_PASS=${DB_PASSWORD:-ChangeMe123!}
- DB_DATABASE=statping
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "https://localhost:443/health"]
interval: 30s
timeout: 10s
retries: 3
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
postgres:
image: postgres:15-alpine
container_name: statping-postgres
restart: unless-stopped
networks:
- backend
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=statping
- POSTGRES_PASSWORD=${DB_PASSWORD:-ChangeMe123!}
- POSTGRES_DB=statping
healthcheck:
test: ["CMD-SHELL", "pg_isready -U statping -d statping"]
interval: 10s
timeout: 5s
retries: 5
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
networks:
backend:
driver: bridge
internal: true
volumes:
statping_data:
driver: local
postgres_data:
driver: local
| Variable | Description | Default | Example |
|---|---|---|---|
NAME |
Status page name | Statping |
My Services |
DESCRIPTION |
Status page description | Status Page |
Uptime Dashboard |
DISABLE_LOGS |
Disable application logs | false |
false |
| Variable | Description | Required | Example |
|---|---|---|---|
DB_CONN |
Database type | Yes | postgres, mysql, sqlite |
DB_HOST |
Database hostname | For postgres/mysql | postgres |
DB_PORT |
Database port | No | 5432, 3306 |
DB_USER |
Database username | For postgres/mysql | statping |
DB_PASS |
Database password | For postgres/mysql | secure-password |
DB_DATABASE |
Database name | For postgres/mysql | statping |
| Variable | Description | Example |
|---|---|---|
VIRTUAL_HOST |
Domain name for routing | status.example.com |
VIRTUAL_PORT |
Internal container port | 8080 |
LETSENCRYPT_HOST |
Domain for SSL certificate | status.example.com |
LETSENCRYPT_EMAIL |
Email for Letβs Encrypt | admin@example.com |
# Add to each service in docker-compose.yml
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
read_only: true # Requires tmpfs for writable directories
tmpfs:
- /tmp
- /run
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Prevents external access
#!/bin/bash
# backup-statping.sh
BACKUP_DIR="/backup/statping"
DATE=$(date +%Y%m%d_%H%M%S)
DB_PASSWORD="YourSecurePassword123!"
mkdir -p "$BACKUP_DIR"
# Backup PostgreSQL database
docker exec statping-postgres pg_dump -U statping -d statping | \
gzip > "$BACKUP_DIR/statping_db_$DATE.sql.gz"
# Backup application data (SQLite, configs, assets)
docker run --rm \
-v statping_data:/data:ro \
-v "$BACKUP_DIR:/backup" \
alpine tar -czf /backup/statping_data_$DATE.tar.gz -C /data .
# Encrypt backup (optional, requires GPG)
# gpg --symmetric --cipher-algo AES256 "$BACKUP_DIR/statping_db_$DATE.sql.gz"
# gpg --symmetric --cipher-algo AES256 "$BACKUP_DIR/statping_data_$DATE.tar.gz"
# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
echo "Backup completed: $DATE"
# Stop application
docker compose stop statping-ng
# Restore PostgreSQL
gunzip -c /backup/statping/statping_db_20260216_120000.sql.gz | \
docker exec -i statping-postgres psql -U statping -d statping
# Restore application data
docker run --rm \
-v statping_data:/data \
-v /backup/statping:/backup \
alpine tar -xzf /backup/statping_data_20260216_120000.tar.gz -C /data
# Restart application
docker compose start statping-ng
Statping-ng exposes Prometheus metrics at /metrics endpoint:
# Test metrics endpoint
curl http://localhost:8080/metrics
# Add Prometheus scrape config
# prometheus.yml
scrape_configs:
- job_name: 'statping-ng'
static_configs:
- targets: ['statping-ng:8080']
metrics_path: '/metrics'
# Add to docker-compose.yml for centralized logging
services:
statping-ng:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
compress: "true"
# Check container status
docker compose ps
# View logs
docker compose logs statping-ng
# Check database connectivity
docker exec -it statping-ng ping -c 3 postgres
# Verify environment variables
docker exec statping-ng env | grep DB_
# Test PostgreSQL connection
docker exec -it statping-postgres psql -U statping -d statping -c "SELECT version();"
# Check database exists
docker exec -it statping-postgres psql -U statping -d statping -c "\l"
# Reset database (WARNING: destroys data)
docker compose down -v
docker volume create postgres_data
docker compose up -d
# Check certificate status
docker exec letsencrypt-companion ls -la /etc/nginx/certs/
# View Let's Encrypt logs
docker compose logs letsencrypt-companion
# Force certificate renewal
docker exec letsencrypt-companion /app/force_renew
# Check container memory
docker stats statping-ng postgres
# Adjust resource limits in docker-compose.yml
# See "Resource Limits" section above
# 1. Backup current data
docker compose down
docker run --rm \
-v statping_data:/data:ro \
-v $(pwd):/backup \
alpine tar -czf /backup/statping_backup_$(date +%Y%m%d).tar.gz -C /data .
# 2. Update image tag in docker-compose.yml
# Change: adamboutcher/statping-ng:0.93.0
# To: adamboutcher/statping-ng:latest (or new version)
# 3. Pull new image
docker compose pull
# 4. Recreate containers
docker compose up -d --force-recreate
# 5. Verify health
docker compose ps
docker compose logs -f statping-ng
Any questions?
Feel free to contact us. Find all contact information on our contact page.