diff --git a/.env.backend.example b/.env.backend.example index 2029b5c05..bd61973aa 100644 --- a/.env.backend.example +++ b/.env.backend.example @@ -10,12 +10,12 @@ DEBUG_DB_VARS=${DEBUG_DB_VARS:-False} ENABLE_LOGGING=${ENABLE_LOGGING:-False} # Logs all prints DEBUG_SQL_QUERY=${DEBUG_SQL_QUERY:-False} # Logs the SQL queries made -DB_NAME=${DB_DEFAULT_NAME:-dashboard} -DB_USER=${DB_DEFAULT_USER:-admin} +DB_NAME=${DB_NAME:-dashboard} +DB_USER=${DB_USER:-admin} DB_PASSWORD=db_password DB_HOST=dashboard_db # Docker can't connect to the ssh tunnel host directly. -DB_PORT=${DB_DEFAULT_PORT:-5432} -DB_ENGINE=${DB_DEFAULT_ENGINE:-django.db.backends.postgresql} +DB_PORT=${DB_PORT:-5432} +DB_ENGINE=${DB_ENGINE:-django.db.backends.postgresql} DB_OPTIONS_CONNECT_TIMEOUT=${DB_OPTIONS_CONNECT_TIMEOUT:-16} # Check docs/monitoring.md docs for more context diff --git a/.env.example b/.env.example index cdeef9461..c3f5301da 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,85 @@ -# Top-level variables that should be used just for the docker-compose setup. -INGESTER_METRICS_PORT=8002 -INGESTER_TREE_NAMES_FILE_DIR=../data +# ============================================================================= +# KernelCI Dashboard — Single .env for docker-compose-next.yml +# ============================================================================= +# Copy this file to .env and edit the values marked CHANGE_ME. +# See DEPLOYMENT.md for full deployment instructions. + +# ----------------------------------------------------------------------------- +# Image Configuration +# ----------------------------------------------------------------------------- +# Pre-built images are pulled from this registry. Override for custom builds. +IMAGE_REGISTRY=ghcr.io +IMAGE_OWNER=kernelci +IMAGE_REPOSITORY=dashboard +IMAGE_TAG=latest + +# ----------------------------------------------------------------------------- +# Database +# ----------------------------------------------------------------------------- +# For production: point to your external PostgreSQL instance. +# For staging (--profile=local-db): keep DB_HOST=dashboard_db. +DB_HOST=dashboard_db +DB_PORT=5432 +DB_NAME=dashboard +DB_USER=admin +DB_PASSWORD=CHANGE_ME +DB_ENGINE=django.db.backends.postgresql +DB_OPTIONS_CONNECT_TIMEOUT=16 + +# Optional: separate application database user/name for setup-dashboard-db.sh. +# Defaults to DB_USER / DB_NAME if not set. +# APP_DB_USER=dashboard +# APP_DB=dashboard + +# ----------------------------------------------------------------------------- +# Django +# ----------------------------------------------------------------------------- +DJANGO_SECRET_KEY=CHANGE_ME +ALLOWED_HOSTS=["backend", "localhost"] +CORS_ALLOWED_ORIGINS=[] +DEBUG=False +SKIP_CRONJOBS=False + +# ----------------------------------------------------------------------------- +# Debug Flags (uncomment to enable) +# ----------------------------------------------------------------------------- +# DEBUG_DB_VARS=True +# ENABLE_LOGGING=True +# DEBUG_SQL_QUERY=True + +# ----------------------------------------------------------------------------- +# Redis +# ----------------------------------------------------------------------------- +# Always "redis" when running inside Docker Compose. +REDIS_HOST=redis + +# ----------------------------------------------------------------------------- +# Monitoring (optional) +# ----------------------------------------------------------------------------- +# See docs/monitoring.md for details. +PROMETHEUS_METRICS_ENABLED=False +# PROMETHEUS_METRICS_PORT=8001 +# PROMETHEUS_MULTIPROC_DIR=/tmp/metrics + +# ----------------------------------------------------------------------------- +# Email / Notifications (optional) +# ----------------------------------------------------------------------------- +# See docs/notifications.md for details. +# EMAIL_HOST=smtp.gmail.com +# EMAIL_PORT=587 +# EMAIL_USE_TLS=True +# EMAIL_HOST_USER=bot@kernelci.org +# EMAIL_HOST_PASSWORD= +# DISCORD_WEBHOOK_URL= + +# ----------------------------------------------------------------------------- +# Backend Volume +# ----------------------------------------------------------------------------- +BACKEND_VOLUME_DIR=/volume_data + +# ----------------------------------------------------------------------------- +# Ingester (only needed with --profile=with_commands) +# ----------------------------------------------------------------------------- +# Host path where the ingester monitors for submission files. INGESTER_SPOOL_DIR=../spool +# INGESTER_METRICS_PORT=8002 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 000000000..bd9a11259 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,206 @@ +# KernelCI Dashboard — Deployment Guide + +This guide covers three deployment scenarios: **development**, **production**, and **staging**. + +## Quick Reference + +| Scenario | Compose File | Database | Profiles | +|----------|-------------|----------|----------| +| Development | `docker-compose.yml` | Local (always on) | `with_commands` for ingester | +| Production | `docker-compose-next.yml` | External PostgreSQL | none (or `with_commands`) | +| Staging | `docker-compose-next.yml` | Local via profile | `local-db` (+ `with_commands`) | + +--- + +## 1. Development (`docker-compose.yml`) + +The development setup builds images locally and uses per-service `.env` files. + +### Setup + +```bash +# Copy all example env files +cp .env.example .env +cp .env.backend.example .env.backend +cp .env.db.example .env.db +cp .env.proxy.example .env.proxy +cp .env.ingester.example .env.ingester +cp .env.pending_aggregations.example .env.pending_aggregations + +# Start all core services (builds images from source) +docker compose up --build -d + +# Include ingester and aggregation processor +docker compose --profile=with_commands up --build -d +``` + +### Rebuilding after code changes + +```bash +# Rebuild and restart just the backend +docker compose up --build -d backend + +# Rebuild everything +docker compose up --build -d +``` + +### Frontend development + +For active frontend work, run the Vite dev server directly: + +```bash +cd dashboard +pnpm install +# Copy the example env file and verify VITE_API_BASE_URL +cp .env.example .env +pnpm dev +``` + +The frontend connects to the backend API via the `VITE_API_BASE_URL` defined in `dashboard/.env` (defaults to `http://localhost:8000`). + +--- + +## 2. Production (`docker-compose-next.yml`, external PostgreSQL) + +Uses pre-built images from GHCR and connects to an external PostgreSQL instance. + +### Setup + +```bash +# 1. Create .env from the template +cp .env.example .env + +# 2. Edit .env — at minimum, set these: +# DB_HOST → your PostgreSQL host +# DB_PORT → your PostgreSQL port (default: 5432) +# DB_PASSWORD → your PostgreSQL password +# DJANGO_SECRET_KEY → a strong random string +# ALLOWED_HOSTS → e.g. ["backend", "your-domain.com"] +# CORS_ALLOWED_ORIGINS → e.g. ["https://your-domain.com"] + +# 3. Start services +docker compose -f docker-compose-next.yml up -d + +# 4. Verify +curl http://localhost/api/ + +# 5. Run database migrations (first deploy or after updates) +docker compose -f docker-compose-next.yml run --rm backend \ + sh -c "chmod +x ./migrate-app-db.sh && ./migrate-app-db.sh" +``` + +### With ingester and aggregation processor + +```bash +# Set INGESTER_SPOOL_DIR in .env to the host path where submissions arrive +docker compose -f docker-compose-next.yml --profile=with_commands up -d +``` + +### Updating to a new version + +```bash +# Pull latest images and restart +docker compose -f docker-compose-next.yml pull +docker compose -f docker-compose-next.yml up -d + +# Run migrations if needed +docker compose -f docker-compose-next.yml run --rm backend \ + sh -c "chmod +x ./migrate-app-db.sh && ./migrate-app-db.sh" +``` + +--- + +## 3. Staging (`docker-compose-next.yml`, local PostgreSQL) + +Uses pre-built images with a local PostgreSQL container via the `local-db` profile. + +### Setup + +```bash +# 1. Create .env from the template +cp .env.example .env + +# 2. Edit .env — at minimum, set these: +# DB_PASSWORD → choose a password for the local postgres +# DJANGO_SECRET_KEY → a random string (can be less strict for staging) +# Keep DB_HOST=dashboard_db (the default) + +# 3. Start the database first (wait for it to be ready) +docker compose -f docker-compose-next.yml --profile=local-db up -d dashboard_db +docker compose -f docker-compose-next.yml exec dashboard_db pg_isready -U admin + +# 4. Start remaining services +docker compose -f docker-compose-next.yml --profile=local-db up -d + +# 5. Verify +curl http://localhost:8000/api/ +curl http://localhost/ +``` + +### With all optional services + +```bash +docker compose -f docker-compose-next.yml --profile=local-db --profile=with_commands up -d +``` + +### Tear down (including database volume) + +```bash +docker compose -f docker-compose-next.yml --profile=local-db down -v +``` + +--- + +## Profile Reference + +| Command | Services | +|---------|----------| +| `docker compose -f docker-compose-next.yml up -d` | redis, backend, dashboard, proxy | +| `... --profile=local-db up -d` | + dashboard_db | +| `... --profile=with_commands up -d` | + ingester, pending_aggregations_processor | +| `... --profile=local-db --profile=with_commands up -d` | All services | + +--- + +## Docker Secrets Support + +The backend entrypoint supports Docker secrets for `DB_PASSWORD`. Instead of setting the password directly in `.env`, you can use: + +```bash +# Create a secrets file +echo "my-secret-password" > backend/runtime/secrets/postgres_password_secret + +# Set in .env or environment: +DB_PASSWORD_FILE=/run/secrets/postgres_password_secret +``` + +The entrypoint's `file_env` function reads the file and exports `DB_PASSWORD`. You cannot set both `DB_PASSWORD` and `DB_PASSWORD_FILE` — the entrypoint will error if both are present. + +--- + +## Migration Notes + +### From `DB_DEFAULT_*` to `DB_*` variables + +Previous versions used `DB_DEFAULT_*` prefixed variables (e.g., `DB_DEFAULT_PASSWORD`, `DB_DEFAULT_HOST`). These have been replaced with `DB_*` variables (e.g., `DB_PASSWORD`, `DB_HOST`). + +**If upgrading from a previous deployment:** + +1. Rename variables in your `.env` / environment: + - `DB_DEFAULT_PASSWORD` → `DB_PASSWORD` + - `DB_DEFAULT_HOST` → `DB_HOST` + - `DB_DEFAULT_PORT` → `DB_PORT` + - `DB_DEFAULT_NAME` → `DB_NAME` + - `DB_DEFAULT_USER` → `DB_USER` + - `DB_DEFAULT_ENGINE` → `DB_ENGINE` + +2. If using Docker secrets: rename `DB_DEFAULT_PASSWORD_FILE` → `DB_PASSWORD_FILE`. + +3. The `DB_DEFAULT` JSON blob environment variable is no longer generated — `settings.py` reads individual `DB_*` variables directly. + +--- + +## Related Documentation + +- [Monitoring Setup](docs/monitoring.md) — Prometheus metrics configuration +- [Notifications](docs/notifications.md) — Email and Discord notification setup diff --git a/backend/setup-dashboard-db.sh b/backend/setup-dashboard-db.sh index 1db268b02..e59e53744 100755 --- a/backend/setup-dashboard-db.sh +++ b/backend/setup-dashboard-db.sh @@ -2,7 +2,7 @@ # Initializes dashboard database roles and databases via direct psql. set -eu -# Resolve env vars: prefer explicit names, fall back to backend naming (DB_DEFAULT_*) +# Resolve env vars: prefer explicit names, fall back to backend naming (DB_*) export DB_HOST="${DB_HOST:-dashboard_db}" export DB_PORT="${DB_PORT:-5432}" export DB_PASSWORD="${DB_PASSWORD:?DB_PASSWORD is required}" diff --git a/backend/utils/docker/backend_entrypoint.sh b/backend/utils/docker/backend_entrypoint.sh index 1d1c39883..4c9f34f49 100755 --- a/backend/utils/docker/backend_entrypoint.sh +++ b/backend/utils/docker/backend_entrypoint.sh @@ -26,7 +26,7 @@ function file_env() { export "$var"="$val" } -file_env DB_DEFAULT_PASSWORD +file_env DB_PASSWORD # Initialize Prometheus metrics before Django starts PROMETHEUS_METRICS_ENABLED=$(echo "$PROMETHEUS_METRICS_ENABLED" | tr '[:upper:]' '[:lower:]') @@ -42,19 +42,6 @@ if [ "$PROMETHEUS_METRICS_ENABLED" = "true" ]; then python3 utils/prometheus_aggregator.py & fi -export DB_DEFAULT="{ - \"ENGINE\": \"${DB_DEFAULT_ENGINE:=django.db.backends.postgresql}\", - \"NAME\": \"${DB_DEFAULT_NAME:=dashboard}\", - \"USER\": \"${DB_DEFAULT_USER:=admin}\", - \"PASSWORD\": \"$DB_DEFAULT_PASSWORD\", - \"HOST\": \"${DB_DEFAULT_HOST:=dashboard_db}\", - \"PORT\": \"${DB_DEFAULT_PORT:=5432}\", - \"CONN_MAX_AGE\": ${DB_DEFAULT_CONN_MAX_AGE:=null}, - \"OPTIONS\": { - \"connect_timeout\": ${DB_DEFAULT_TIMEOUT:=16} - } -}" - chmod +x ./setup-dashboard-db.sh ./setup-dashboard-db.sh diff --git a/docker-compose-next.yml b/docker-compose-next.yml index 74b2ea33e..18ef49ae5 100644 --- a/docker-compose-next.yml +++ b/docker-compose-next.yml @@ -1,6 +1,13 @@ +# docker-compose-next.yml — Pre-built images, single .env file +# Usage: +# Production (external DB): docker compose -f docker-compose-next.yml up -d +# Staging (local DB): docker compose -f docker-compose-next.yml --profile=local-db up -d +# With ingester/aggregation: docker compose -f docker-compose-next.yml --profile=with_commands up -d +# All services: docker compose -f docker-compose-next.yml --profile=local-db --profile=with_commands up -d +# See DEPLOYMENT.md for full instructions. + volumes: backend-data: - runtime-data: static-data: dashboard-db-data: @@ -11,14 +18,22 @@ networks: services: dashboard_db: image: postgres:17 - env_file: - - .env.db + environment: + POSTGRES_USER: ${DB_USER:-admin} + POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD must be set in .env} + POSTGRES_DB: ${DB_NAME:-dashboard} volumes: - dashboard-db-data:/var/lib/postgresql/data networks: - private ports: - "5434:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-admin}"] + interval: 5s + timeout: 5s + retries: 5 + profiles: ["local-db"] redis: image: redis:8.0-M04-alpine @@ -29,7 +44,18 @@ services: - 6379:6379 backend: - image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER}/${IMAGE_REPOSITORY}/dashboard-backend:${IMAGE_TAG} + image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER:-kernelci}/${IMAGE_REPOSITORY:-dashboard}/dashboard-backend:${IMAGE_TAG:-latest} + entrypoint: "./utils/docker/backend_entrypoint.sh" + command: + - poetry + - run + - gunicorn + - kernelCI.wsgi:application + - --workers=5 + - --forwarded-allow-ips=* + - --bind=0.0.0.0:8000 + - --timeout=250 + env_file: .env volumes: - backend-data:${BACKEND_VOLUME_DIR:-/volume_data} restart: always @@ -45,19 +71,14 @@ services: protocol: tcp depends_on: - redis - - dashboard_db - env_file: - - .env.backend - environment: - DB_DEFAULT_HOST: ${DB_DEFAULT_HOST:-dashboard_db} dashboard: - image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER}/${IMAGE_REPOSITORY}/dashboard-frontend:${IMAGE_TAG} + image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER:-kernelci}/${IMAGE_REPOSITORY:-dashboard}/dashboard-frontend:${IMAGE_TAG:-latest} volumes: - static-data:/data/static proxy: - image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER}/${IMAGE_REPOSITORY}/dashboard-proxy:${IMAGE_TAG} + image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER:-kernelci}/${IMAGE_REPOSITORY:-dashboard}/dashboard-proxy:${IMAGE_TAG:-latest} restart: always depends_on: - backend @@ -70,5 +91,56 @@ services: - target: 80 published: 80 protocol: tcp - env_file: - - .env.proxy + env_file: .env + environment: + PROXY_TARGET: ${PROXY_TARGET:-http://backend:8000} + + ingester: + image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER:-kernelci}/${IMAGE_REPOSITORY:-dashboard}/dashboard-backend:${IMAGE_TAG:-latest} + command: + - poetry + - run + - python3 + - manage.py + - monitor_submissions + - --spool-dir + - /app/spool + env_file: .env + environment: + SKIP_CRONJOBS: "True" + volumes: + - backend-data:${BACKEND_VOLUME_DIR:-/volume_data} + - ${INGESTER_SPOOL_DIR:-../spool}:/app/spool + restart: always + networks: + - private + - public + ports: + - target: ${INGESTER_METRICS_PORT:-8002} + published: ${INGESTER_METRICS_PORT:-8002} + protocol: tcp + depends_on: + - redis + profiles: ["with_commands"] + + pending_aggregations_processor: + image: ${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_OWNER:-kernelci}/${IMAGE_REPOSITORY:-dashboard}/dashboard-backend:${IMAGE_TAG:-latest} + command: + - poetry + - run + - python3 + - manage.py + - process_pending_aggregations + - --loop + - --interval + - "5" + env_file: .env + environment: + SKIP_CRONJOBS: "True" + restart: always + networks: + - private + - public + depends_on: + - redis + profiles: ["with_commands"] diff --git a/docker-compose.yml b/docker-compose.yml index 43e3debd5..0dc62e82b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,8 @@ services: - --bind=0.0.0.0:8000 - --timeout=250 entrypoint: "./utils/docker/backend_entrypoint.sh" + secrets: + - postgres_password_secret depends_on: - redis - dashboard_db diff --git a/docs/Onboarding.md b/docs/Onboarding.md index 594b8ff2d..9939e2423 100644 --- a/docs/Onboarding.md +++ b/docs/Onboarding.md @@ -125,7 +125,7 @@ Other environment variables can be set as needed. 3. Start up the services with the command: ```bash -docker compose up build -d +docker compose up --build -d ``` After starting the services, you can check if your Docker containers are running with: