Service Reference
Service Reference
This reference covers the Docker services used across the production compose variants, their configuration, ports, and resource limits.
Nuxt BFF Network Pattern
LAREX frontend includes a Nuxt server-side BFF layer:
- Browser clients call the frontend host and same-origin
/api/*. - Nuxt server routes proxy to backend using
NUXT_API_BASE_INTERNAL. - Backend can stay internal to the server/container network for normal app usage.
Some variants still include direct backend/API routing for operational scenarios.
Deployment Variants
| Compose File | Includes Traefik | Includes Keycloak |
|---|---|---|
compose.prod.yaml | No | Yes (bundled) |
compose.prod.external-keycloak.yaml | No | No (external) |
compose.prod.local.yaml | Yes | Yes (bundled, local-friendly config) |
compose.prod.traefik.yaml | Yes | Yes (bundled, TLS via Traefik) |
Service Overview
| Service | Image | Port | Health Check | Present In |
|---|---|---|---|---|
| traefik | traefik:v3.6.x | 80, 443 (or 80, 8081 local) | - | compose.prod.local.yaml, compose.prod.traefik.yaml |
| postgres | postgres:18.2-alpine | internal only by default | pg_isready | All variants |
| keycloak | quay.io/keycloak/keycloak:26.5.4 | 8090 (default no-Traefik) / internal / Traefik-routed | /health/ready | All except compose.prod.external-keycloak.yaml |
| app | backend Dockerfile.prod | 8080 (no-Traefik variants) / Traefik-routed | /actuator/health | All variants |
| frontend | frontend Dockerfile.prod | 3000 (no-Traefik variants) / Traefik-routed | /api/health/backend | All variants |
Traefik (Reverse Proxy)
This section applies to compose.prod.traefik.yaml and compose.prod.local.yaml.
Configuration
traefik:
image: traefik:v3.6.7
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
volumes:
- ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
restart: unless-stopped
Entry Points
| Entry Point | Port | Purpose |
|---|---|---|
| web | 80 | HTTP (redirects to websecure) |
| websecure | 443 | HTTPS with TLS |
Routers
| Service | Rule | TLS |
|---|---|---|
| frontend | Host(FRONTEND_HOSTNAME) | LetsEncrypt |
| app | Host(API_HOSTNAME) | LetsEncrypt (optional direct API exposure) |
| keycloak | Host(KEYCLOAK_HOSTNAME) | LetsEncrypt |
Resource Limits
deploy:
resources:
limits:
cpus: '1'
memory: 512M
PostgreSQL (Database)
Configuration
postgres:
image: postgres:17.5-alpine
environment:
POSTGRES_DB: larexdb
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d larexdb"]
interval: 10s
timeout: 5s
retries: 5
Ports
| Port | Description |
|---|---|
| 5432 | PostgreSQL connection |
Resource Limits
deploy:
resources:
limits:
cpus: '2'
memory: 2G
Volumes
| Volume | Purpose |
|---|---|
postgres_data | Database files |
Keycloak (Identity Provider)
This section applies to bundled-Keycloak variants only (compose.prod.yaml, compose.prod.local.yaml, compose.prod.traefik.yaml).
Configuration
keycloak:
image: quay.io/keycloak/keycloak:26.3.3
command: start
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/larexdb
KC_DB_USERNAME: ${POSTGRES_USER}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
KC_HOSTNAME_STRICT: true
KC_HOSTNAME_STRICT_HTTPS: true
KC_HTTP_ENABLED: false
KC_PROXY: edge
KC_HEALTH_ENABLED: true
volumes:
- keycloak_data:/opt/keycloak/data
Health Check
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q '200 OK'"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
Resource Limits
deploy:
resources:
limits:
cpus: '2'
memory: 2G
Volumes
| Volume | Purpose |
|---|---|
keycloak_data | Keycloak data and logs |
Backend (Spring Boot)
Configuration
app:
build:
context: backend
dockerfile: Dockerfile.prod
environment:
SPRING_PROFILES_ACTIVE: prod
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
KEYCLOAK_RESOURCE_CLIENT_ID: ${KEYCLOAK_RESOURCE_CLIENT_ID}
KEYCLOAK_ADMIN_SERVER_URL: ${KEYCLOAK_ADMIN_SERVER_URL}
KEYCLOAK_ADMIN_REALM: ${KEYCLOAK_ADMIN_REALM}
KEYCLOAK_ADMIN_CLIENT_SECRET: ${KEYCLOAK_ADMIN_CLIENT_SECRET}
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: ${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI}
CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN}
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: ${MAIL_PORT}
MAIL_USERNAME: ${MAIL_USERNAME}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_FROM: ${MAIL_FROM}
MAIL_ENABLED: ${MAIL_ENABLED:-true}
MAIL_SMTP_AUTH: ${MAIL_SMTP_AUTH:-true}
MAIL_SMTP_STARTTLS: ${MAIL_SMTP_STARTTLS:-true}
Health Check
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
Resource Limits
deploy:
resources:
limits:
cpus: '2'
memory: 2G
Logging
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Frontend (Nuxt.js)
The frontend service is present in all variants. In Traefik-based variants it is typically routed by hostname; in no-Traefik variants it is exposed directly on host port 3000.
Configuration
frontend:
build:
context: frontend
dockerfile: Dockerfile.prod
environment:
NUXT_API_BASE_INTERNAL: ${NUXT_API_BASE_INTERNAL}
NUXT_OAUTH_KEYCLOAK_SERVER_URL: ${NUXT_OAUTH_KEYCLOAK_SERVER_URL}
NUXT_OAUTH_KEYCLOAK_SERVER_URL_INTERNAL: ${NUXT_OAUTH_KEYCLOAK_SERVER_URL_INTERNAL}
NUXT_OAUTH_KEYCLOAK_REALM: ${NUXT_OAUTH_KEYCLOAK_REALM}
NUXT_OAUTH_KEYCLOAK_CLIENT_ID: ${NUXT_OAUTH_KEYCLOAK_CLIENT_ID}
NUXT_OAUTH_KEYCLOAK_CLIENT_SECRET: ${NUXT_OAUTH_KEYCLOAK_CLIENT_SECRET}
NUXT_OAUTH_KEYCLOAK_REDIRECT_URL: ${NUXT_OAUTH_KEYCLOAK_REDIRECT_URL}
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health/backend || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
Resource Limits
deploy:
resources:
limits:
cpus: '1'
memory: 1G
Networks
Network layout differs by variant:
compose.prod.yamlandcompose.prod.external-keycloak.yamluse a simple backend networkcompose.prod.local.yamluses the default compose network plussocket-proxycompose.prod.traefik.yamlusesbackend,web, andsocket-proxy
networks:
backend:
driver: bridge
web:
driver: bridge
socket-proxy:
driver: bridge
Network Diagram
┌─────────────────────────────────────────────────────────┐
│ web │
│ ┌─────────┐ ┌─────────┐ ┌─────────────────────────┐ │
│ │ traefik │──│ frontend│ │ app │ │
│ └─────────┘ └─────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲ ▲
│ │
┌────────┴────────┐ ┌─────────┴────────┐
│ backend │ │ keycloak │
│ ┌───────────┐ │ │ ┌─────────────┐ │
│ │ postgres │ │ │ │ keycloak │ │
│ └───────────┘ │ │ └─────────────┘ │
└─────────────────┘ └───────────────────┘
Volume Reference
| Volume | Service | Description |
|---|---|---|
postgres_data | postgres | Database files |
keycloak_data | keycloak | Keycloak data |
letsencrypt_data | traefik | SSL certificates |
Restart Policies
All services use unless-stopped:
restart: unless-stopped
This ensures services restart after:
- Docker daemon restart
- System reboot
- Container crash