Skip to content

Production Guide ​

A checklist and guide for deploying ArchForge to a production environment.

Pre-Deployment Checklist ​

#TaskPriorityNotes
1Set production secretsCriticalNo defaults — DB_PASSWORD, Redis, RSA, Sa-Token Redis, etc.
2Change RSA private keyCriticalGenerate a new RSA key pair; pass it as ARCH_FORGE_RSA_PRIVATE_KEY (the admin does not start without it)
3Change PostgreSQL passwordCriticalUse a strong password
4Enable captchaHighSet arch-forge.captcha.enabled: true
5Configure PostgreSQL (master/slave)HighSet up replication if needed
6Configure RedisHighDedicated instance with a password — REDIS_PASSWORD (docker-compose.prod.yml and docker-compose.staging.yml start Redis with --requirepass and fail without it)
7Enable FlywayHighSet arch-forge.flyway.enabled: true
8Set JPA DDL to validateHighspring.jpa.hibernate.ddl-auto: validate
9Configure HTTPSHighTerminate SSL at Nginx or load balancer
10Set up database backupsHighAutomated daily backups
11Check OTLP samplingMediumSAMPLING_PROBABILITY defaults to 10% in prod/staging
12Review log levelsMediumAvoid DEBUG in production
13Set memory limitsMediumJVM flags or Docker resource limits
14Set CORS_ALLOWED_ORIGINSCriticalExplicit origins, no * — every profile except dev/test refuses to start otherwise
15Set ALERT_WEBHOOK_URLHighWhere Alertmanager delivers alerts (see Observability)
16Set WEB_PUBLIC_URLHighPublic URL of the C-end site (links in mails and notifications)

docker-compose.prod.yml refuses to start until DB_PASSWORD, REDIS_PASSWORD, ARCH_FORGE_RSA_PRIVATE_KEY, CORS_ALLOWED_ORIGINS and WEB_PUBLIC_URL are set (scripts/prod/.env.example lists them). A contract test keeps the compose files in step with every variable the prod/staging profiles require.

Generate Secrets ​

Production secrets ​

Auth is Sa-Token (UUID tokens in Redis), not a signed JWT. Production must inject secrets via environment variables; missing ARCH_FORGE_RSA_PRIVATE_KEY or DB_PASSWORD fails startup / compose. Do not rely on YAML defaults.

bash
# Example: generate an RSA transport key and a Redis/DB password
openssl rand -base64 32
./archforge init --write --profile prod

Copy values into .env / your secret manager. Do not commit application-prod.yaml secrets.

RSA Key Pair ​

Generate a new RSA private key:

bash
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl pkcs8 -topk8 -inform PEM -outform DER -in private.pem -out private.der -nocrypt
base64 private.der

Set the Base64-encoded private key in arch-forge.rsa-private-key (ARCH_FORGE_RSA_PRIVATE_KEY). The server derives the public key and hands it to the admin login page through GET /admin/auth/getConfig; the page encrypts the password with RSA-OAEP / SHA-256. That needs a secure context — serve prod over HTTPS (see Authentication).

Production Configuration ​

Create archforge-server-admin/src/main/resources/application-prod.yaml from the example:

bash
cp application-prod.yaml.example application-prod.yaml

Key Settings ​

yaml
server:
  port: 8080

spring:
  jpa:
    hibernate:
      ddl-auto: validate         # Flyway manages DDL
  datasource:
    dynamic:
      primary: user_master
      datasource:
        user_master:
          driver-class-name: org.postgresql.Driver
          url: jdbc:postgresql://master:5432/archforge
          username: ${DB_USERNAME}
          password: ${DB_PASSWORD}
        user_slave:
          driver-class-name: org.postgresql.Driver
          url: jdbc:postgresql://slave:5432/archforge
          username: ${DB_USERNAME}
          password: ${DB_PASSWORD}
  data:
    redis:
      host: ${REDIS_HOST:redis}
      port: 6379
      password: ${REDIS_PASSWORD}

sa-token:
  timeout: 86400                 # 24 hours for production
arch-forge:
  captcha:
    enabled: true
  flyway:
    enabled: true
  monitor:
    enabled: true
  embedded:
    redis: false                 # Use real Redis
    postgresql: false            # Use real PostgreSQL

management:
  server:
    port: ${MANAGEMENT_SERVER_PORT:8089}   # actuator off the business port (web: 8091)
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  tracing:
    sampling:
      probability: 0.1           # 10% sampling in production

Database Setup ​

Install PostgreSQL 17 ​

Option A: Docker (recommended for most deployments):

bash
docker run -d --name archforge-pg \
  --restart unless-stopped \
  -e POSTGRES_USER=archforge \
  -e POSTGRES_PASSWORD=${DB_PASSWORD} \
  -e POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=en_US.UTF-8" \
  -v postgres-data:/var/lib/postgresql/data \
  -p 5432:5432 postgres:17-alpine

Option B: Native install (Ubuntu):

bash
sudo apt install -y postgresql-17
sudo -u postgres createuser --createdb archforge
sudo -u postgres psql -c "ALTER USER archforge PASSWORD '<your-password>';"

Create Databases ​

bash
psql -h <master-host> -U archforge -c \
  "CREATE DATABASE archforge_user ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8';"

Master/Slave Replication ​

For read/write split with dynamic-datasource, set up PostgreSQL streaming replication between master and slave instances. ArchForge routes queries automatically via the @DS annotation and datasource groups configured in application-prod.yaml.

Refer to the PostgreSQL Replication Documentation for detailed setup instructions.

First Deployment ​

On first startup, Flyway creates all tables and seeds initial data automatically:

bash
# Start application — Flyway runs V1 (schema), V2 (data), V3 (menus)
SPRING_PROFILES_ACTIVE=prod java -jar archforge-server-admin.jar

Subsequent Deployments ​

Flyway automatically detects and runs new migration scripts. Always:

  1. Backup the database before deploying
  2. Review migration scripts via code review
  3. Test migrations in the test environment first

See Database Migration for the full guide.

HTTPS Setup ​

Option A: Nginx SSL Termination ​

nginx
server {
    listen 443 ssl http2;
    server_name admin.yourdomain.com;

    ssl_certificate     /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://archforge:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }
}

server {
    listen 80;
    server_name admin.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

Option B: Cloud Load Balancer ​

If using AWS ALB, GCP Load Balancer, or similar, terminate SSL at the load balancer and proxy HTTP to Nginx on port 80.

Backup Strategy ​

Database Backup ​

bash
# Daily automated backup
pg_dump -h <master-host> -U archforge archforge_user | gzip > backup_$(date +%Y%m%d).sql.gz

Schedule via cron for daily backups and retain 30 days:

bash
# /etc/cron.d/archforge-backup
0 2 * * * archforge pg_dump -h localhost -U archforge archforge_user | gzip > /backups/archforge_$(date +\%Y\%m\%d).sql.gz && find /backups -name "archforge_*.sql.gz" -mtime +30 -delete

Application Backup ​

  • Docker volumes for PostgreSQL data: postgres-data
  • Application logs: docker/logs/
  • Configuration: application-prod.yaml (keep in a secure vault, not git)

Monitoring ​

Health Checks and Probes ​

bash
curl http://localhost:8080/livez     # liveness: the process only
curl http://localhost:8080/readyz    # readiness: + database + Redis

In prod (and staging) the actuator itself listens on the management port — admin 8089, web 8091, override with MANAGEMENT_SERVER_PORT in the service's environment (the compose files do not set it). Prometheus scrapes 8089/8091 — change docker/observability/prometheus/prometheus.yml together with it. /actuator/health on the business port returns 404 there, so point orchestrator probes at /livez and /readyz. Never publish the management port.

Prometheus Metrics ​

Scrape /actuator/prometheus on the management port from inside the network: backend:8089 and backend-web:8091 in docker/docker-compose.prod.yml, which joins both services to the archforge-observability network. The bundled Prometheus config discovers them by DNS — see Observability.

Released under the MIT License.