Deployment

Production Deployment

Deploy LAREX to production using Docker Compose.

Production Deployment

This guide covers deploying LAREX with Docker Compose using multiple production deployment variants.

Deployment Variants

Compose FileUse CaseNotes
compose.prod.yamlDefault production deploymentNo Traefik, bundled Keycloak, direct host ports (3000, 8080, 8090) bound to localhost by default
compose.prod.external-keycloak.yamlProduction with existing KeycloakNo Traefik, uses external Keycloak instance
compose.prod.local.yamlProduction-like local testingTraefik + *.localhost, Mailpit, no certificates
compose.prod.traefik.yamlAdvanced/secondary productionTraefik + LetsEncrypt + bundled Keycloak (includes optional direct API router)

Request Topology (Nuxt BFF)

For normal application usage:

  • Browser clients access the frontend host.
  • Frontend uses same-origin /api/* routes.
  • Nuxt server routes call backend internally via NUXT_API_BASE_INTERNAL.

This allows keeping backend service endpoints internal-only in hardened deployments.

Prerequisites

Before deploying, ensure you have:

  • Docker Engine 24+ with Docker Compose v2
  • Firewall access for the ports required by your chosen variant
  • Domain name + DNS only for the Traefik (compose.prod.traefik.yaml) variant
  • LetsEncrypt / SSL/TLS only for the Traefik (compose.prod.traefik.yaml) variant

Server Requirements

ResourceMinimumRecommended
CPU2 cores4+ cores
Memory4 GB8+ GB
Storage20 GB50+ GB SSD
Network10 Mbps100 Mbps

Environment Setup

1. Create Variant Environment File

Start from the example file in deployment/env/ that matches your compose variant, then use --env-file in Docker Compose commands:

# Default production preset (no Traefik, bundled Keycloak)
cp deployment/env/.env.prod.example .env.prod

# Production-like local preset
cp deployment/env/.env.prod.local.example .env.prod.local

# Production with external Keycloak (no Traefik)
cp deployment/env/.env.prod.external-keycloak.example .env.prod.external-keycloak

# Advanced/secondary Traefik + LetsEncrypt preset
cp deployment/env/.env.prod.traefik.example .env.prod.traefik

Example contents for .env.prod (default preset):

# ============================================
# LAREX Production Environment Configuration
# ============================================

# Version
VERSION=0.1.0

# ============================================
# PostgreSQL Configuration
# ============================================
POSTGRES_USER=larex_prod
POSTGRES_PASSWORD=your-secure-password-here
POSTGRES_DB=larexdb

# ============================================
# Bundled Keycloak (default + Traefik variants)
# ============================================
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=your-secure-admin-password
KEYCLOAK_ADMIN_CLIENT_SECRET=your-keycloak-client-secret
KEYCLOAK_RESOURCE_CLIENT_ID=larex-backend
KEYCLOAK_HOSTNAME=localhost:8090

# ============================================
# Application / Frontend OAuth Configuration
# ============================================
CORS_ALLOWED_ORIGIN=http://localhost:3000
NUXT_SESSION_PASSWORD=replace-with-random-32-plus-character-secret
NUXT_OAUTH_KEYCLOAK_SERVER_URL=http://localhost:8090
NUXT_OAUTH_KEYCLOAK_SERVER_URL_INTERNAL=http://keycloak:8080
NUXT_OAUTH_KEYCLOAK_REALM=larex-prod
NUXT_OAUTH_KEYCLOAK_CLIENT_ID=larex-frontend
NUXT_OAUTH_KEYCLOAK_CLIENT_SECRET=your-frontend-client-secret
NUXT_OAUTH_KEYCLOAK_REDIRECT_URL=http://localhost:3000/auth/keycloak
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8090/realms/larex-prod
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=http://keycloak:8080/realms/larex-prod/protocol/openid-connect/certs

# ============================================
# Email Configuration (Optional)
# ============================================
MAIL_ENABLED=false
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=noreply@your-domain.com
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true

# ============================================
# Traefik Variant Only (compose.prod.traefik.yaml)
# ============================================
FRONTEND_HOSTNAME=app.your-domain.com
API_HOSTNAME=api.your-domain.com
KEYCLOAK_HOSTNAME=auth.your-domain.com
ACME_EMAIL=admin@your-domain.com

For the external Keycloak and Traefik variants, see the variant-specific examples in Environment Variables. The no-Traefik presets bind published ports to 127.0.0.1 by default. Set LAREX_PUBLISH_IP=0.0.0.0 only behind a firewall and/or TLS reverse proxy.

2. Configure DNS Records (Traefik Variant Only)

Ensure your DNS records point to the server:

Record TypeHostValue
Aapp.your-domain.com
Aapi.your-domain.com (only if direct API exposure is enabled)
Aauth.your-domain.com

3. Prepare the Server

# SSH into your server
ssh user@your-server

# Clone the repository
git clone <repository-url>
cd larex

# Copy and edit the env file for your selected variant
cp deployment/env/.env.prod.example .env.prod
nano .env.prod

# Ensure Docker is running
docker --version
docker compose version

Deployment Steps

1. Build Production Images

# Build all Docker images
task build:docker

# Or directly with Docker Compose
docker compose --env-file .env.prod -f compose.prod.yaml build

2. Start Production Services

# Start all services
docker compose --env-file .env.prod -f compose.prod.yaml up -d

# View logs during startup
docker compose --env-file .env.prod -f compose.prod.yaml logs -f

3. Verify Deployment

# Check service status
docker compose --env-file .env.prod -f compose.prod.yaml ps

# Check service health (default preset)
curl http://localhost:8080/actuator/health
curl http://localhost:3000

4. Access Services

ServiceURLDescription
Frontendhttp://localhost:3000Main application (default preset)
Backend APIhttp://localhost:8080Internal/admin endpoint in default preset (not required for browser clients)
Swagger UIhttp://localhost:8080/swagger-ui.htmlInternal/admin API documentation
Keycloakhttp://localhost:8090Bundled identity provider (default preset)

5. Other Deployment Variants

# Production-like local preset (Traefik + localhost hostnames + Mailpit)
docker compose --env-file .env.prod.local -f compose.prod.local.yaml up -d

# Production without Traefik using an external Keycloak
docker compose --env-file .env.prod.external-keycloak -f compose.prod.external-keycloak.yaml up -d

# Advanced/secondary Traefik + LetsEncrypt preset
docker compose --env-file .env.prod.traefik -f compose.prod.traefik.yaml up -d

Service Management

Starting Services

# Start all services
docker compose -f compose.prod.yaml up -d

# Start specific service
docker compose -f compose.prod.yaml up -d frontend

Stopping Services

# Stop all services
docker compose -f compose.prod.yaml down

# Stop specific service
docker compose -f compose.prod.yaml down frontend

Viewing Logs

# All services
docker compose -f compose.prod.yaml logs -f

# Specific service
docker compose -f compose.prod.yaml logs -f frontend
docker compose -f compose.prod.yaml logs -f app
docker compose -f compose.prod.yaml logs -f keycloak

Restarting Services

# Restart all services
docker compose -f compose.prod.yaml restart

# Restart specific service
docker compose -f compose.prod.yaml restart frontend

Updating Services

# Pull latest images
docker compose -f compose.prod.yaml pull

# Rebuild and restart
docker compose -f compose.prod.yaml up -d --build

SSL/TLS Certificates (Traefik Variant Only)

LetsEncrypt automatically provisions certificates when using compose.prod.traefik.yaml:

  1. First request triggers certificate generation
  2. Certificates are stored in letsencrypt_data volume
  3. Automatic renewal is configured

Certificate Information

# View certificate details
docker compose -f compose.prod.traefik.yaml exec traefik cat /letsencrypt/acme.json | jq .

Manual Certificate Renewal

# Certificates renew automatically, but you can force:
docker compose -f compose.prod.traefik.yaml restart traefik

Health Checks

All services include health checks:

ServiceHealth EndpointInterval
Backend/actuator/health30s
Frontend/api/health/backend30s
Keycloak/health/ready30s
PostgreSQLpg_isready10s

Rolling Updates

Deploy updates with zero downtime:

# Pull latest changes
git pull origin main

# Build new images
docker compose -f compose.prod.yaml build

# Rolling restart
docker compose -f compose.prod.yaml up -d

# Monitor rollout
docker compose -f compose.prod.yaml logs -f

Backup and Recovery

Database Backup

# Create backup
docker compose -f compose.prod.yaml exec postgres pg_dump -U larex larexdb > backup_$(date +%Y%m%d).sql

# Restore backup
docker compose -f compose.prod.yaml exec -T postgres psql -U larex -d larexdb < backup_20240101.sql

Volume Backup

# Backup volumes
docker run --rm -v larex_postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_$(date +%Y%m%d).tar.gz -C /data .
docker run --rm -v larex_keycloak_data:/data -v $(pwd):/backup alpine tar czf /backup/keycloak_$(date +%Y%m%d).tar.gz -C /data .
docker run --rm -v larex_app_data:/data -v $(pwd):/backup alpine tar czf /backup/app_data_$(date +%Y%m%d).tar.gz -C /data .

File-Based Dump/Reseed (Projects + Utilities)

The backend now supports large, path-based backup jobs under /api/v1/admin/backup/*.

# Dump all file-based data to /mnt/data/backups
LAREX_API_TOKEN=<global-admin-token> ./scripts/larex-backup.sh dump --output /mnt/data/backups

# Reseed from an existing dump archive
LAREX_API_TOKEN=<global-admin-token> ./scripts/larex-backup.sh reseed \
  --source /mnt/data/backups/larex-dump-20260221-120000.larex-dump.zip \
  --output /mnt/data/backups

Monitoring

View Container Stats

docker stats

Check Disk Usage

docker system df

View Resource Usage

docker compose -f compose.prod.yaml top

Troubleshooting

Services Not Starting

# Check logs
docker compose -f compose.prod.yaml logs

# Check specific service
docker compose -f compose.prod.yaml logs app

SSL Certificate Issues

# Remove certificates and re-issue
docker compose -f compose.prod.traefik.yaml down
docker volume rm larex_letsencrypt_data
docker compose -f compose.prod.traefik.yaml up -d

Database Connection Issues

# Check database health
docker compose -f compose.prod.yaml exec postgres pg_isready -U larex

# Check connection string
docker compose -f compose.prod.yaml exec app curl http://postgres:5432

Next Steps

Copyright © 2026