Skip to content

Docker Deployment

FluxiQ SPB is fully containerized with multi-stage Docker builds for each Elixir service, the Vue 3 frontend, and the documentation site. This guide covers building images, running with Docker Compose, and configuring production deployments.

Architecture Overview

docker-compose.yml
  ├── api_gateway        (port 4000)
  ├── auth_service       (port 4001)
  ├── bacen_gateway      (port 4002)
  ├── message_processor  (port 4003)
  ├── user_management    (port 4004)
  ├── transaction_service (port 4005)
  ├── securities_service (port 4006)
  ├── settlement_service (port 4007)
  ├── forex_service      (port 4008)
  ├── cash_service       (port 4009)
  ├── extract_service    (port 4010)
  ├── frontend           (port 3000)
  ├── bacen_simulator    (port 4020)
  ├── postgres           (port 5432)
  ├── ibm_mq             (port 1414)
  ├── prometheus          (port 9090)
  └── grafana            (port 3001)

Elixir Service Dockerfile

Each Elixir microservice uses the same multi-stage Dockerfile pattern:

dockerfile
# Build stage
FROM hexpm/elixir:1.16.1-erlang-26.2.2-alpine-3.19.1 AS builder

RUN apk add --no-cache build-base git

WORKDIR /app

ENV MIX_ENV=prod

# Install hex and rebar
RUN mix local.hex --force && mix local.rebar --force

# Install dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
RUN mix deps.compile

# Compile application
COPY config config
COPY lib lib
COPY priv priv
RUN mix compile

# Build release
RUN mix release

# Runtime stage
FROM alpine:3.19 AS runtime

RUN apk add --no-cache libstdc++ openssl ncurses-libs

WORKDIR /app

# Create non-root user
RUN addgroup -g 1000 appuser && adduser -u 1000 -G appuser -s /bin/sh -D appuser

COPY --from=builder /app/_build/prod/rel/service ./
RUN chown -R appuser:appuser /app

USER appuser

ENV HOME=/app
ENV MIX_ENV=prod

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:${PORT}/health || exit 1

ENTRYPOINT ["bin/service"]
CMD ["start"]

Docker Compose

Development Configuration

yaml
# docker-compose.yml
version: "3.8"

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: spb_dev
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  ibm_mq:
    image: icr.io/ibm-messaging/mq:9.3.4.0-r1
    environment:
      LICENSE: accept
      MQ_QMGR_NAME: QM1
      MQ_APP_PASSWORD: passw0rd
    ports:
      - "1414:1414"
      - "9443:9443"
    volumes:
      - mq_data:/mnt/mqm

  api_gateway:
    build:
      context: ./services/api_gateway
      dockerfile: Dockerfile
    ports:
      - "4000:4000"
    environment:
      PORT: "4000"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      AUTH_SERVICE_URL: http://auth_service:4001
      TRANSACTION_SERVICE_URL: http://transaction_service:4005
      SECURITIES_SERVICE_URL: http://securities_service:4006
      SETTLEMENT_SERVICE_URL: http://settlement_service:4007
      FOREX_SERVICE_URL: http://forex_service:4008
      CASH_SERVICE_URL: http://cash_service:4009
      EXTRACT_SERVICE_URL: http://extract_service:4010
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  auth_service:
    build:
      context: ./services/auth_service
      dockerfile: Dockerfile
    ports:
      - "4001:4001"
    environment:
      PORT: "4001"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
      JWT_SECRET: ${JWT_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  bacen_gateway:
    build:
      context: ./services/bacen_gateway
      dockerfile: Dockerfile
    ports:
      - "4002:4002"
    environment:
      PORT: "4002"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
      BACEN_MODE: simulator
      BACEN_ISPB: "12345678"
      BACEN_MQ_HOST: ibm_mq
      BACEN_MQ_PORT: "1414"
      BACEN_MQ_CHANNEL: BACEN.SVRCONN
      BACEN_MQ_QUEUE_MANAGER: QM1
    depends_on:
      postgres:
        condition: service_healthy
      ibm_mq:
        condition: service_started
    restart: unless-stopped

  message_processor:
    build:
      context: ./services/message_processor
      dockerfile: Dockerfile
    ports:
      - "4003:4003"
    environment:
      PORT: "4003"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
      BROADWAY_CONCURRENCY: "10"
      BROADWAY_MAX_DEMAND: "50"
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  transaction_service:
    build:
      context: ./services/transaction_service
      dockerfile: Dockerfile
    ports:
      - "4005:4005"
    environment:
      PORT: "4005"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  securities_service:
    build:
      context: ./services/securities_service
      dockerfile: Dockerfile
    ports:
      - "4006:4006"
    environment:
      PORT: "4006"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  settlement_service:
    build:
      context: ./services/settlement_service
      dockerfile: Dockerfile
    ports:
      - "4007:4007"
    environment:
      PORT: "4007"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  forex_service:
    build:
      context: ./services/forex_service
      dockerfile: Dockerfile
    ports:
      - "4008:4008"
    environment:
      PORT: "4008"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  cash_service:
    build:
      context: ./services/cash_service
      dockerfile: Dockerfile
    ports:
      - "4009:4009"
    environment:
      PORT: "4009"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  extract_service:
    build:
      context: ./services/extract_service
      dockerfile: Dockerfile
    ports:
      - "4010:4010"
    environment:
      PORT: "4010"
      DATABASE_URL: ecto://postgres:postgres@postgres:5432/spb_dev
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      VITE_API_URL: http://localhost:4000
    depends_on:
      - api_gateway
    restart: unless-stopped

  bacen_simulator:
    build:
      context: ./simulator
      dockerfile: Dockerfile
    ports:
      - "4020:4020"
    environment:
      PORT: "4020"
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:v2.49.0
    ports:
      - "9090:9090"
    volumes:
      - ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    restart: unless-stopped

  grafana:
    image: grafana/grafana:10.3.1
    ports:
      - "3001:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - ./infra/grafana/dashboards:/var/lib/grafana/dashboards
      - ./infra/grafana/provisioning:/etc/grafana/provisioning
      - grafana_data:/var/lib/grafana
    depends_on:
      - prometheus
    restart: unless-stopped

volumes:
  postgres_data:
  mq_data:
  prometheus_data:
  grafana_data:

Building Images

bash
# Build all services
docker compose build

# Build a specific service
docker compose build api_gateway

# Build with no cache
docker compose build --no-cache

# Build with BuildKit for faster builds
DOCKER_BUILDKIT=1 docker compose build

Running the Platform

bash
# Start all services
docker compose up -d

# Start with logs visible
docker compose up

# Start specific services
docker compose up -d postgres ibm_mq api_gateway auth_service

# View logs
docker compose logs -f api_gateway

# Check service health
docker compose ps

# Run database migrations
docker compose exec api_gateway bin/service eval "ApiGateway.Release.migrate()"

# Open an IEx shell in a running service
docker compose exec api_gateway bin/service remote

# Stop all services
docker compose down

# Stop and remove volumes (resets all data)
docker compose down -v

Production Configuration

For production deployments, use the production override file:

yaml
# docker-compose.prod.yml
version: "3.8"

services:
  api_gateway:
    image: registry.fluxiq.io/spb/api_gateway:${TAG:-latest}
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
        reservations:
          memory: 256M
          cpus: "0.25"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Deploy with:

bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Environment Variables Reference

VariableRequiredDefaultDescription
DATABASE_URLYes-PostgreSQL connection string
SECRET_KEY_BASEYes-Phoenix secret key (min 64 chars)
JWT_SECRETYes-JWT signing secret (min 64 chars)
BACEN_MODENosimulatorsimulator or production
BACEN_ISPBYes-Institution ISPB code (8 digits)
BACEN_MQ_HOSTNolocalhostIBM MQ hostname
BACEN_MQ_PORTNo1414IBM MQ port
BROADWAY_CONCURRENCYNo10Broadway processor concurrency
BROADWAY_MAX_DEMANDNo50Broadway max demand per processor
MFA_ENABLEDNotrueEnable multi-factor authentication
LOG_LEVELNoinfoLog level (debug, info, warn, error)

Health Checks

Every service exposes a /health endpoint. Docker Compose uses these for dependency ordering and restart policies. In production, configure your load balancer to use these endpoints for routing decisions.

bash
# Check all service health at once
for port in 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010; do
  echo "Port $port: $(curl -s http://localhost:$port/health | jq -r .status)"
done

Plataforma de Integracao BACEN/SPB