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 buildRunning 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 -vProduction 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 -dEnvironment Variables Reference
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | Yes | - | PostgreSQL connection string |
SECRET_KEY_BASE | Yes | - | Phoenix secret key (min 64 chars) |
JWT_SECRET | Yes | - | JWT signing secret (min 64 chars) |
BACEN_MODE | No | simulator | simulator or production |
BACEN_ISPB | Yes | - | Institution ISPB code (8 digits) |
BACEN_MQ_HOST | No | localhost | IBM MQ hostname |
BACEN_MQ_PORT | No | 1414 | IBM MQ port |
BROADWAY_CONCURRENCY | No | 10 | Broadway processor concurrency |
BROADWAY_MAX_DEMAND | No | 50 | Broadway max demand per processor |
MFA_ENABLED | No | true | Enable multi-factor authentication |
LOG_LEVEL | No | info | Log 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