Production Deployment
Production Deployment
This guide covers deploying LAREX with Docker Compose using multiple production deployment variants.
Deployment Variants
| Compose File | Use Case | Notes |
|---|---|---|
compose.prod.yaml | Default production deployment | No Traefik, bundled Keycloak, direct host ports (3000, 8080, 8090) bound to localhost by default |
compose.prod.external-keycloak.yaml | Production with existing Keycloak | No Traefik, uses external Keycloak instance |
compose.prod.local.yaml | Production-like local testing | Traefik + *.localhost, Mailpit, no certificates |
compose.prod.traefik.yaml | Advanced/secondary production | Traefik + 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
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores | 4+ cores |
| Memory | 4 GB | 8+ GB |
| Storage | 20 GB | 50+ GB SSD |
| Network | 10 Mbps | 100 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 Type | Host | Value |
|---|---|---|
| A | app.your-domain.com | |
| A | api.your-domain.com | |
| A | auth.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
| Service | URL | Description |
|---|---|---|
| Frontend | http://localhost:3000 | Main application (default preset) |
| Backend API | http://localhost:8080 | Internal/admin endpoint in default preset (not required for browser clients) |
| Swagger UI | http://localhost:8080/swagger-ui.html | Internal/admin API documentation |
| Keycloak | http://localhost:8090 | Bundled 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:
- First request triggers certificate generation
- Certificates are stored in
letsencrypt_datavolume - 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:
| Service | Health Endpoint | Interval |
|---|---|---|
| Backend | /actuator/health | 30s |
| Frontend | /api/health/backend | 30s |
| Keycloak | /health/ready | 30s |
| PostgreSQL | pg_isready | 10s |
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
- Environment Variables - Complete variable reference
- Service Reference - Service configuration details
- CI/CD - Continuous integration and deployment