docker-compose.yml
Rama main · réplica de lectura del repositorio original.
services:
# Database (with auto-generated TLS)
postgres:
build:
context: ./scripts
dockerfile: Dockerfile.postgres
container_name: ninerlog-db
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-ninerlog}
POSTGRES_USER: ${POSTGRES_USER:-ninerlog}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "-E UTF8"
volumes:
- postgres_data:/var/lib/postgresql
expose:
- "5432"
command:
- "postgres"
- "-c"
- "ssl=on"
- "-c"
- "ssl_cert_file=/var/lib/postgresql/ssl/server.crt"
- "-c"
- "ssl_key_file=/var/lib/postgresql/ssl/server.key"
- "-c"
- "log_connections=on"
- "-c"
- "log_disconnections=on"
- "-c"
- "log_statement=ddl"
- "-c"
- "password_encryption=scram-sha-256"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ninerlog}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- ninerlog-network
# Backend API
api:
image: ghcr.io/fjaeckel/ninerlog-api:latest
container_name: ninerlog-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
# Database (SSL required — self-signed cert between containers)
DATABASE_URL: postgresql://${POSTGRES_USER:-ninerlog}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-ninerlog}?sslmode=require
# Server
PORT: 3000
GIN_MODE: ${GIN_MODE:-release}
# JWT (no defaults — must be set in .env)
JWT_SECRET: ${JWT_SECRET}
REFRESH_SECRET: ${REFRESH_SECRET}
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-15m}
REFRESH_EXPIRES_IN: ${REFRESH_EXPIRES_IN:-7d}
# CORS (must match your domain)
CORS_ORIGIN: ${CORS_ORIGIN}
# WebAuthn / Passkeys (optional — leave WEBAUTHN_RP_ID empty to disable)
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-}
WEBAUTHN_RP_NAME: ${WEBAUTHN_RP_NAME:-NinerLog}
WEBAUTHN_RP_ORIGINS: ${WEBAUTHN_RP_ORIGINS:-}
# OIDC single sign-on (optional — leave OIDC_ISSUER empty to disable).
# WARNING: setting OIDC_ISSUER switches the whole server to OIDC mode —
# password login, registration, TOTP and passkeys are all disabled.
# See docs/OIDC.md before enabling.
OIDC_ISSUER: ${OIDC_ISSUER:-}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
OIDC_REDIRECT_URL: ${OIDC_REDIRECT_URL:-}
OIDC_POST_LOGIN_REDIRECT: ${OIDC_POST_LOGIN_REDIRECT:-}
OIDC_PROVIDER_NAME: ${OIDC_PROVIDER_NAME:-}
OIDC_SCOPES: ${OIDC_SCOPES:-}
OIDC_NAME_CLAIM: ${OIDC_NAME_CLAIM:-}
OIDC_LINK_BY_VERIFIED_EMAIL: ${OIDC_LINK_BY_VERIFIED_EMAIL:-}
OIDC_TRUST_EMAIL_VERIFIED: ${OIDC_TRUST_EMAIL_VERIFIED:-}
OIDC_LOGIN_STATE_TTL: ${OIDC_LOGIN_STATE_TTL:-}
OIDC_HANDOFF_TTL: ${OIDC_HANDOFF_TTL:-}
# Admin
ADMIN_EMAIL: ${ADMIN_EMAIL:-}
# Logging
LOG_LEVEL: ${LOG_LEVEL:-info}
# SMTP
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-noreply@ninerlog.com}
# Notification checker interval (default: 1h)
NOTIFICATION_CHECK_INTERVAL: ${NOTIFICATION_CHECK_INTERVAL:-1h}
# Cloud backups (optional — leave empty to disable the feature)
# 32 random bytes, base64-encoded. Generate with: openssl rand -base64 32
BACKUP_CREDENTIALS_KEY: ${BACKUP_CREDENTIALS_KEY:-}
expose:
- "3000"
networks:
- ninerlog-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 15s
timeout: 10s
retries: 8
start_period: 60s
# Frontend (nginx reverse proxy + static assets)
frontend:
image: ghcr.io/fjaeckel/ninerlog-frontend:latest
container_name: ninerlog-frontend
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api/v1}
VITE_ENV: ${VITE_ENV:-production}
VITE_APP_NAME: ${APP_NAME:-${VITE_APP_NAME:-}}
BETA_PASSWORD: ${BETA_PASSWORD:-}
TLS_DOMAIN: ${TLS_DOMAIN}
ports:
- "${FRONTEND_PORT:-80}:80"
- "${FRONTEND_TLS_PORT:-443}:443"
expose:
# Internal-only nginx stub_status port for the nginx-exporter sidecar.
# Never published to the host — see nginx.conf.
- "8080"
volumes:
# Let's Encrypt certs (shared with certbot container)
- letsencrypt_certs:/etc/letsencrypt:ro
# ACME challenge webroot (shared with certbot container)
- certbot_webroot:/var/www/certbot:ro
networks:
- ninerlog-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# Prometheus exporter for the frontend nginx.
# Scrapes the internal-only stub_status endpoint and exposes Prometheus
# metrics on port 9113. The port is NOT published here — reveal it to your
# monitoring host via docker-compose.override.yml, exactly like the API.
# See docs/MONITORING.md.
nginx-exporter:
image: nginx/nginx-prometheus-exporter:1.5.3
container_name: ninerlog-nginx-exporter
restart: unless-stopped
depends_on:
frontend:
condition: service_healthy
command:
- "--nginx.scrape-uri=http://frontend:8080/stub_status"
expose:
- "9113"
networks:
- ninerlog-network
# Prometheus exporter for PostgreSQL.
# Connects to the database over TLS (sslmode=require, same as the API) and
# exposes Prometheus metrics on port 9187. The port is NOT published here —
# reveal it to your monitoring host via docker-compose.override.yml, exactly
# like the API. See docs/MONITORING.md.
postgres-exporter:
image: quay.io/prometheuscommunity/postgres-exporter:v0.20.1
container_name: ninerlog-postgres-exporter
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DATA_SOURCE_NAME: "postgresql://${POSTGRES_USER:-ninerlog}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-ninerlog}?sslmode=require"
expose:
- "9187"
networks:
- ninerlog-network
# Scheduled PostgreSQL backups (compressed)
db-backup:
build:
context: ./scripts
dockerfile: Dockerfile.db-backup
container_name: ninerlog-db-backup
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
PGHOST: postgres
PGPORT: "5432"
PGUSER: ${POSTGRES_USER:-ninerlog}
PGDATABASE: ${POSTGRES_DB:-ninerlog}
PGPASSWORD: ${POSTGRES_PASSWORD}
BACKUP_INTERVAL: ${BACKUP_INTERVAL:-21600}
BACKUP_RETENTION: ${BACKUP_RETENTION:-30}
volumes:
- ${BACKUP_PATH:-./backups}:/backups
networks:
- ninerlog-network
# Let's Encrypt certificate manager
# Initial cert request (run once):
# docker compose run --rm certbot certonly --webroot -w /var/www/certbot \
# -d yourdomain.com --agree-tos --email your@email.com
# After that, the container auto-renews every 12 hours.
certbot:
image: certbot/certbot:latest
container_name: ninerlog-certbot
volumes:
- letsencrypt_certs:/etc/letsencrypt
- certbot_webroot:/var/www/certbot
entrypoint: certbot
command: ["renew", "--webroot", "-w", "/var/www/certbot", "--quiet"]
depends_on:
- frontend
restart: "no"
volumes:
postgres_data:
driver: local
letsencrypt_certs:
driver: local
certbot_webroot:
driver: local
networks:
ninerlog-network:
driver: bridge