DO

Docker

infrastructure v25+

Docker

Version: 25+

Overview

Docker provides containerization for the Prismatic Platform, ensuring consistent environments across development, testing, staging, and production. The platform uses multi-stage Docker builds to create minimal production images that include only the compiled BEAM release, significantly reducing attack surface and deployment size. Docker eliminates the environment parity problem that plagues complex multi-service applications by packaging the entire runtime -- from operating system libraries to compiled application code -- into reproducible, immutable containers.

The Prismatic Platform's Docker configuration handles the complexity of building an Elixir umbrella application with 90 apps, native NIF dependencies (including KuzuDB bindings), and multiple service integrations. Multi-stage builds separate compilation (with dev tools, compilers, and build dependencies) from the final runtime image (Alpine Linux with only the BEAM runtime and system libraries), resulting in production images under 100MB despite the platform's scale. This separation ensures that build tools, source code, and development dependencies never appear in production containers.

Docker Compose orchestrates the platform's local development environment, spinning up PostgreSQL, Redis, Meilisearch, KuzuDB, and Ollama alongside the Elixir application for a complete development stack that mirrors production. A single docker compose up gives any developer a fully operational platform instance with all dependencies running and properly configured.

Key Features

Docker provides the containerization foundation that makes the Prismatic Platform's deployment reproducible and environment-agnostic.

  • Multi-Stage Builds: Separate build and runtime stages, keeping final images minimal (< 100MB) and secure
  • Layer Caching: Efficient rebuilds by caching dependency layers -- only recompiled when mix.exs or mix.lock change
  • Compose: Multi-service orchestration for local development with health checks, dependency ordering, and persistent volumes
  • Health Checks: Container health monitoring that Fly.io uses for rolling deployments and automatic container replacement
  • Volume Mounts: Persistent data storage for PostgreSQL, Meilisearch, and Ollama across container restarts
  • Networking: Service discovery via Docker DNS, allowing containers to reach each other by service name
  • BuildKit: Parallel layer building and cache mounts for faster CI builds
  • Security Scanning: Integration with vulnerability scanning tools to audit base images and dependencies
Build StageContentsSizePurpose
depsMix dependencies, compiled libs~800MBDependency resolution and compilation
buildFull application, compiled code~1.2GBApplication compilation and release building
runtimeBEAM release only, Alpine Linux~80MBProduction execution, minimal attack surface

Platform Integration

Docker packages the platform for consistent deployment across all environments. The Dockerfile uses three stages: dependencies, compilation, and runtime. Each stage is optimized for caching efficiency so that changing application code does not trigger a full dependency rebuild.

# Stage 1: Dependencies
FROM elixir:1.19-otp-27-alpine AS deps
RUN apk add --no-cache git build-base
WORKDIR /app

ENV MIX_ENV=prod
COPY mix.exs mix.lock ./
COPY apps/*/mix.exs apps/*/
RUN mix deps.get --only prod && mix deps.compile

# Stage 2: Compile application
FROM deps AS build
COPY config config/
COPY apps apps/
COPY priv priv/
RUN mix compile --warnings-as-errors
RUN mix phx.digest
RUN mix release prismatic

# Stage 3: Minimal runtime image
FROM alpine:3.19 AS runtime
RUN apk add --no-cache libstdc++ openssl ncurses-libs
RUN addgroup -S app && adduser -S app -G app

WORKDIR /app
COPY --from=build /app/_build/prod/rel/prismatic ./
RUN chown -R app:app /app
USER app

ENV PHX_SERVER=true
EXPOSE 4000
HEALTHCHECK --interval=30s --timeout=5s \
  CMD wget -qO- http://localhost:4000/health || exit 1

CMD ["/app/bin/prismatic", "start"]

Docker Compose defines the full development stack with service dependencies, health checks, and persistent volumes:

# docker-compose.yml
services:
  prismatic:
    build: .
    ports:
      - "4000:4000"
      - "4004:4004"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
      meilisearch:
        condition: service_started
    environment:
      - DATABASE_URL=ecto://postgres:postgres@postgres/prismatic_dev
      - REDIS_URL=redis://redis:6379
      - MEILI_URL=http://meilisearch:7700

  postgres:
    image: timescale/timescaledb:latest-pg16
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: postgres
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data

  meilisearch:
    image: getmeili/meilisearch:v1.6
    volumes:
      - meilidata:/meili_data
    environment:
      MEILI_ENV: development

  ollama:
    image: ollama/ollama:latest
    volumes:
      - ollamadata:/root/.ollama

volumes:
  pgdata:
  redisdata:
  meilidata:
  ollamadata:

Architecture

Docker serves as the deployment packaging layer in the platform's infrastructure architecture, bridging the gap between development environments and production deployments.

EnvironmentDocker RoleConfiguration
DevelopmentDocker Compose for servicesdocker-compose.yml with volume mounts
CI/CDBuild and test containerGitLab CI with Docker-in-Docker
StagingFull container deploymentFly.io with fly.toml configuration
ProductionImmutable container releaseFly.io with rolling deployments and health checks

The platform's container architecture follows the principle of immutable infrastructure: production containers are never modified after creation. Any change requires building a new image, pushing it to the registry, and deploying it through the CI/CD pipeline. This approach eliminates configuration drift and ensures every deployment is reproducible.

Development Workflow:
  Code Change --> Docker Build (multi-stage) --> Image Registry
                                                      |
  CI/CD Pipeline: Test --> Quality Gates --> Push Image
                                                      |
  Deployment: Fly.io Pull --> Health Check --> Rolling Deploy
                                                      |
  Production: Immutable Container --> Health Monitoring

Performance Characteristics

Docker build and deployment performance is optimized through layer caching, multi-stage builds, and BuildKit features. The platform's build times are designed to keep the CI/CD feedback loop under 10 minutes.

OperationDurationOptimization
Full Docker build (cold)8-12 minutesMulti-stage with dependency caching
Incremental build (deps cached)2-4 minutesOnly application code recompiled
Image push to registry30-60 secondsLayer deduplication
Container startup3-5 secondsBEAM runtime initialization
Health check response< 10ms/health endpoint
Rolling deployment30-60 secondsZero-downtime with health gates
Production image size~80MBAlpine base, release-only
Development compose up15-30 secondsAll services with health checks

Configuration

The .dockerignore file excludes development artifacts, keeping the build context small and preventing secrets from leaking into the image.

# .dockerignore
_build/
deps/
.git/
.env*
node_modules/
priv/plts/
*.secret.exs
.claude/
.aiad/

Fly.io deployment configuration integrates with Docker for production deployments:

# fly.toml
app = "prismatic"
primary_region = "fra"

[build]
  dockerfile = "Dockerfile"

[env]
  PHX_HOST = "prismatic-prod.fly.dev"
  PHX_SERVER = "true"

[http_service]
  internal_port = 4000
  force_https = true

  [[http_service.checks]]
    grace_period = "10s"
    interval = "30s"
    method = "GET"
    path = "/health"
    timeout = "5s"

Best Practices

The platform enforces Docker best practices that balance build speed, security, and deployment reliability.

  • Never run as root -- add a non-root user in the runtime stage for defense-in-depth (the Dockerfile creates an app user)
  • Pin base image versions -- use alpine:3.19 not alpine:latest to ensure reproducible builds across time
  • Use --warnings-as-errors in the build stage -- catch compilation warnings before they reach production
  • Cache dependency layers -- copy mix.exs and mix.lock before application code so dependency changes trigger rebuilds independently
  • Add health checks -- the HEALTHCHECK instruction enables orchestrators to detect unhealthy containers and replace them automatically
  • Use .dockerignore -- prevent .env files, .git history, and build artifacts from inflating the build context or leaking secrets
  • Minimize layers -- combine related RUN commands to reduce image layer count and size
  • Scan images for vulnerabilities -- integrate image scanning in CI to catch known CVEs in base images and dependencies

Comparison

Docker was chosen as the platform's containerization technology for its ecosystem maturity, tooling support, and integration with Fly.io deployment infrastructure. The multi-stage build pattern also enables reproducible builds across CI environments, ensuring that the exact same binary artifact is tested in staging and deployed to production.

CriterionDockerPodmanNixDirect BEAM Release
Ecosystem maturityExcellentGoodGoodN/A
Multi-service orchestrationComposePod conceptNix shellsManual
CI/CD integrationUniversalGrowingNicheBuild tool specific
Image registry supportUniversalDocker-compatibleNix cacheN/A
Fly.io integrationNativeCompatibleLimitedSupported
Build reproducibilityLayer cachingLayer cachingHermeticMix release
Development experienceCompose upSimilar to DockerDifferent paradigmNative Elixir
  • Elixir - Application language compiled inside Docker build stages
  • BEAM - Runtime included in the production Docker image
  • PostgreSQL - Database container in both development and production stacks
  • Git - Source control triggering Docker builds through CI/CD
  • Erlang/OTP - OTP release system (mix release) that Docker packages
  • All 90 Prismatic Platform applications are containerized in a single umbrella release Docker image
  • prismatic_api - API gateway exposed on port 4004 in the container
  • prismatic_web - Main web application exposed on port 4000 in the container
  • prismatic_perimeter - Security scanning services running within the containerized environment

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL