Deployment

Service Reference

Reference for Docker services, ports, health checks, and resource limits.

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 FileIncludes TraefikIncludes Keycloak
compose.prod.yamlNoYes (bundled)
compose.prod.external-keycloak.yamlNoNo (external)
compose.prod.local.yamlYesYes (bundled, local-friendly config)
compose.prod.traefik.yamlYesYes (bundled, TLS via Traefik)

Service Overview

ServiceImagePortHealth CheckPresent In
traefiktraefik:v3.6.x80, 443 (or 80, 8081 local)-compose.prod.local.yaml, compose.prod.traefik.yaml
postgrespostgres:18.2-alpineinternal only by defaultpg_isreadyAll variants
keycloakquay.io/keycloak/keycloak:26.5.48090 (default no-Traefik) / internal / Traefik-routed/health/readyAll except compose.prod.external-keycloak.yaml
appbackend Dockerfile.prod8080 (no-Traefik variants) / Traefik-routed/actuator/healthAll variants
frontendfrontend Dockerfile.prod3000 (no-Traefik variants) / Traefik-routed/api/health/backendAll 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 PointPortPurpose
web80HTTP (redirects to websecure)
websecure443HTTPS with TLS

Routers

ServiceRuleTLS
frontendHost(FRONTEND_HOSTNAME)LetsEncrypt
appHost(API_HOSTNAME)LetsEncrypt (optional direct API exposure)
keycloakHost(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

PortDescription
5432PostgreSQL connection

Resource Limits

deploy:
  resources:
    limits:
      cpus: '2'
      memory: 2G

Volumes

VolumePurpose
postgres_dataDatabase 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

VolumePurpose
keycloak_dataKeycloak 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.yaml and compose.prod.external-keycloak.yaml use a simple backend network
  • compose.prod.local.yaml uses the default compose network plus socket-proxy
  • compose.prod.traefik.yaml uses backend, web, and socket-proxy
networks:
  backend:
    driver: bridge
  web:
    driver: bridge
  socket-proxy:
    driver: bridge

Network Diagram

┌─────────────────────────────────────────────────────────┐
│                        web                              │
│  ┌─────────┐  ┌─────────┐  ┌─────────────────────────┐  │
│  │ traefik │──│ frontend│  │          app            │  │
│  └─────────┘  └─────────┘  └─────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
         ▲                        ▲
         │                        │
┌────────┴────────┐    ┌─────────┴────────┐
│     backend     │    │      keycloak     │
│  ┌───────────┐  │    │  ┌─────────────┐  │
│  │  postgres │  │    │  │ keycloak    │  │
│  └───────────┘  │    │  └─────────────┘  │
└─────────────────┘    └───────────────────┘

Volume Reference

VolumeServiceDescription
postgres_datapostgresDatabase files
keycloak_datakeycloakKeycloak data
letsencrypt_datatraefikSSL certificates

Restart Policies

All services use unless-stopped:

restart: unless-stopped

This ensures services restart after:

  • Docker daemon restart
  • System reboot
  • Container crash
Copyright © 2026