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 -dVerify 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 # DocsView 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_serviceStop the Stack
bash
# Stop all containers
docker compose down
# Stop and remove volumes (data loss)
docker compose down -vBuilding Images
Build All Images
bash
docker compose buildBuild 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.0Production 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: 1GCertificate 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.pemLogging
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