Skip to content

Docker Deployment

This guide covers building and deploying FluxiQ PIX using Docker containers.

Prerequisites

  • Docker >= 24.0
  • Docker Compose >= 2.20
  • At least 4 GB RAM available for containers

Container Architecture

┌─────────────────────────────────────────────────┐
│                  Docker Network                  │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │
│  │   DICT   │  │   SPI    │  │  Settlement  │  │
│  │  :4001   │  │  :4002   │  │    :4003     │  │
│  └────┬─────┘  └────┬─────┘  └──────┬───────┘  │
│       │              │               │          │
│  ┌────▼──────────────▼───────────────▼───────┐  │
│  │              Internal Network              │  │
│  └────┬──────────────┬───────────────┬───────┘  │
│       │              │               │          │
│  ┌────▼─────┐  ┌─────▼────┐  ┌──────▼──────┐  │
│  │PostgreSQL│  │  Redis   │  │    NATS     │  │
│  │  :5432   │  │  :6379   │  │   :4222     │  │
│  └──────────┘  └──────────┘  └─────────────┘  │
│                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐  │
│  │  Admin   │  │   User   │  │    Docs      │  │
│  │  Portal  │  │  Portal  │  │    Site      │  │
│  │  :3000   │  │  :3001   │  │   :8080      │  │
│  └──────────┘  └──────────┘  └──────────────┘  │
└─────────────────────────────────────────────────┘

Docker Compose

Development Stack

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

services:
  # --- Infrastructure ---

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: pix_dev
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

  nats:
    image: nats:2.10-alpine
    ports:
      - "4222:4222"
      - "8222:8222"  # Monitoring
    command: ["--jetstream", "--store_dir", "/data"]
    volumes:
      - nats_data:/data
    healthcheck:
      test: ["CMD", "nats-server", "--signal", "ldm"]
      interval: 10s
      timeout: 5s
      retries: 3

  # --- Backend Services ---

  dict_service:
    build:
      context: .
      dockerfile: apps/dict_service/Dockerfile
    ports:
      - "4001:4001"
    environment:
      DATABASE_URL: postgres://postgres:postgres@postgres:5432/pix_dev
      REDIS_URL: redis://redis:6379/0
      NATS_URL: nats://nats:4222
      DICT_BASE_URL: https://dict-h.pi.rsfn.net.br:16522/api/v2
      CERT_PATH: /app/certs/client.pem
      KEY_PATH: /app/certs/client_key.pem
      CA_PATH: /app/certs/ca_chain.pem
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      ISPB: ${ISPB}
      PHX_PORT: 4001
    volumes:
      - ./certs:/app/certs:ro
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      nats:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4001/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  spi_service:
    build:
      context: .
      dockerfile: apps/spi_service/Dockerfile
    ports:
      - "4002:4002"
    environment:
      DATABASE_URL: postgres://postgres:postgres@postgres:5432/pix_dev
      REDIS_URL: redis://redis:6379/0
      NATS_URL: nats://nats:4222
      SPI_BASE_URL: https://spi-h.pi.rsfn.net.br:16522
      CERT_PATH: /app/certs/client.pem
      KEY_PATH: /app/certs/client_key.pem
      CA_PATH: /app/certs/ca_chain.pem
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      ISPB: ${ISPB}
      PHX_PORT: 4002
    volumes:
      - ./certs:/app/certs:ro
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      nats:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4002/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  settlement_service:
    build:
      context: .
      dockerfile: apps/settlement_service/Dockerfile
    ports:
      - "4003:4003"
    environment:
      DATABASE_URL: postgres://postgres:postgres@postgres:5432/pix_dev
      REDIS_URL: redis://redis:6379/0
      NATS_URL: nats://nats:4222
      DICT_SERVICE_URL: http://dict_service:4001
      SPI_SERVICE_URL: http://spi_service:4002
      CERT_PATH: /app/certs/client.pem
      KEY_PATH: /app/certs/client_key.pem
      CA_PATH: /app/certs/ca_chain.pem
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      ISPB: ${ISPB}
      PHX_PORT: 4003
    volumes:
      - ./certs:/app/certs:ro
    depends_on:
      dict_service:
        condition: service_healthy
      spi_service:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4003/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  # --- Frontend Portals ---

  admin_portal:
    build:
      context: ./frontend/admin
      dockerfile: Dockerfile
    ports:
      - "3000:8080"
    depends_on:
      settlement_service:
        condition: service_healthy

  user_portal:
    build:
      context: ./frontend/user
      dockerfile: Dockerfile
    ports:
      - "3001:8080"
    depends_on:
      settlement_service:
        condition: service_healthy

  # --- Documentation ---

  docs:
    build:
      context: ./docs-site
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  postgres_data:
  redis_data:
  nats_data:

Backend Dockerfile

Each backend service uses a multi-stage Elixir release build:

dockerfile
# apps/dict_service/Dockerfile (representative for all services)
FROM elixir:1.16-otp-26-alpine 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

# Copy umbrella root config
COPY mix.exs mix.lock ./
COPY config/config.exs config/prod.exs config/runtime.exs config/

# Copy app dependencies
COPY apps/shared/mix.exs apps/shared/
COPY apps/dict_service/mix.exs apps/dict_service/

RUN mix deps.get --only prod
RUN mix deps.compile

# Copy application code
COPY apps/shared apps/shared
COPY apps/dict_service apps/dict_service

# Compile and build release
RUN mix compile
RUN mix release dict_service

# --- Runtime ---
FROM alpine:3.19

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

WORKDIR /app

COPY --from=builder /app/_build/prod/rel/dict_service ./

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
RUN chown -R appuser:appgroup /app
USER appuser

ENV PHX_PORT=4001
EXPOSE 4001

HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:${PHX_PORT}/health || exit 1

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

Running the Stack

Start All Services

bash
# Create .env file with required variables
cat > .env << 'EOF'
SECRET_KEY_BASE=your-secret-key-base-minimum-64-characters-long-replace-this
ISPB=12345678
EOF

# Start infrastructure first
docker compose up -d postgres redis nats

# Wait for infrastructure to be healthy
docker compose up -d --wait

# Run database migrations
docker compose exec dict_service bin/dict_service eval "DictService.Release.migrate()"

# Start all services
docker compose up -d

Verify Health

bash
# Check all container statuses
docker compose ps

# Check individual service health
curl http://localhost:4001/health   # DICT
curl http://localhost:4002/health   # SPI
curl http://localhost:4003/health   # Settlement
curl http://localhost:8080/health   # Docs

View Logs

bash
# All services
docker compose logs -f

# Specific service
docker compose logs -f dict_service

# Last 100 lines
docker compose logs --tail=100 spi_service

Stop the Stack

bash
# Stop all containers
docker compose down

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

Building Images

Build All Images

bash
docker compose build

Build with Tags

bash
# Build with version tag
docker build -t fluxiq/dict-service:1.0.0 -f apps/dict_service/Dockerfile .
docker build -t fluxiq/spi-service:1.0.0 -f apps/spi_service/Dockerfile .
docker build -t fluxiq/settlement-service:1.0.0 -f apps/settlement_service/Dockerfile .
docker build -t fluxiq/admin-portal:1.0.0 frontend/admin/
docker build -t fluxiq/user-portal:1.0.0 frontend/user/
docker build -t fluxiq/docs:1.0.0 docs-site/

Push to Registry

bash
# Tag and push to your container registry
docker tag fluxiq/dict-service:1.0.0 registry.example.com/pix/dict-service:1.0.0
docker push registry.example.com/pix/dict-service:1.0.0

Production Considerations

Resource Limits

Set appropriate resource limits in docker-compose for production:

yaml
services:
  dict_service:
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
        reservations:
          cpus: "1.0"
          memory: 1G

Certificate Mounting

In production, mount certificates as read-only secrets:

yaml
services:
  dict_service:
    secrets:
      - source: client_cert
        target: /app/certs/client.pem
      - source: client_key
        target: /app/certs/client_key.pem
      - source: ca_chain
        target: /app/certs/ca_chain.pem

secrets:
  client_cert:
    file: ./certs/client.pem
  client_key:
    file: ./certs/client_key.pem
  ca_chain:
    file: ./certs/ca_chain.pem

Logging

Configure structured JSON logging for production:

yaml
services:
  dict_service:
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "5"
        tag: "{{.Name}}"

Database Backups

Add a backup container for scheduled PostgreSQL backups:

yaml
services:
  db_backup:
    image: prodrigestivill/postgres-backup-local:16
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_DB: pix_prod
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      SCHEDULE: "@daily"
      BACKUP_KEEP_DAYS: 30
    volumes:
      - ./backups:/backups
    depends_on:
      postgres:
        condition: service_healthy

FluxiQ PIX - Plataforma Brasileira de Pagamento Instantaneo