Skip to Content
DeploymentDocker Compose

Docker Compose Deployment

Multi-container deployment with individually distributed services. Each component — API server, frontend, m3-plugin-runner, and Traefik reverse proxy — runs in its own container for independent scaling, logging, and lifecycle management.

Docker Compose is the recommended deployment method for production and active development. For a simpler single-container option, see All-in-One.

Suitable For

  • Production deployments
  • Active development (with hot reload)
  • Teams requiring individual service scaling
  • Environments where services need independent monitoring

Components

ServiceImagePortDescription
apidocker/api.Dockerfile3500Node.js tRPC API server
frontenddocker/frontend.Dockerfile80nginx serving React SPA
m3-plugin-runnerservices/m3-plugin-runner/Dockerfile8080Python container lifecycle manager
traefiktraefik:v3.08880, 8443, 8881Reverse proxy with dashboard

Quick Start

Create docker-compose.yml

Save the following as docker-compose.yml:

docker-compose.yml
services: api: image: marieai/m3-forge:latest container_name: marie-api environment: NODE_ENV: production PORT: 3500 DATABASE_URL: ${DATABASE_URL} JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET} JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET} S3_ENDPOINT_URL: ${S3_ENDPOINT_URL} S3_BUCKET_NAME: ${S3_BUCKET_NAME:-marie} S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID} S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY} S3_REGION: ${S3_REGION:-us-east-1} S3_ADDRESSING_STYLE: ${S3_ADDRESSING_STYLE:-path} FRONTEND_URL: ${FRONTEND_URL:-http://localhost} API_URL: ${API_URL:-http://localhost} ports: - "${API_PORT:-3500}:3500" networks: - marie-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3500/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s restart: unless-stopped m3-plugin-runner: image: marieai/m3-plugin-runner:latest container_name: marie-m3-plugin-runner environment: HOST: 0.0.0.0 PORT: 8080 APPS_DIR: /data/apps DOMAIN: ${WEBAPP_DOMAIN:-apps.localhost} DOCKER_NETWORK: marie-network RUNNER_API_KEY: ${RUNNER_API_KEY:-} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:${TRAEFIK_HTTP_PORT:-8880}} DEBUG: ${DEBUG:-false} volumes: - /var/run/docker.sock:/var/run/docker.sock - plugin-data:/data/apps ports: - "${WEBAPP_RUNNER_PORT:-8090}:8080" networks: - marie-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s restart: unless-stopped traefik: image: traefik:v3.0 container_name: marie-traefik command: - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=marie-network" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--api.dashboard=true" - "--api.insecure=true" - "--log.level=${TRAEFIK_LOG_LEVEL:-INFO}" ports: - "${TRAEFIK_HTTP_PORT:-8880}:80" - "${TRAEFIK_HTTPS_PORT:-8443}:443" - "${TRAEFIK_DASHBOARD_PORT:-8881}:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: - marie-network restart: unless-stopped networks: marie-network: name: marie-network driver: bridge volumes: plugin-data: name: marie-plugin-data

Create .env

Save the following as .env and fill in the values:

.env
# Required DATABASE_URL=postgresql://postgres:123456@host.docker.internal:5432/studio S3_ENDPOINT_URL=http://host.docker.internal:8000 S3_ACCESS_KEY_ID=MARIEACCESSKEY S3_SECRET_ACCESS_KEY=MARIESECRETACCESSKEY # Generate with: openssl rand -hex 32 JWT_ACCESS_SECRET=replace-with-generated-secret JWT_REFRESH_SECRET=replace-with-generated-secret # Optional # S3_BUCKET_NAME=marie # S3_REGION=us-east-1 # WEBAPP_DOMAIN=apps.localhost # RUNNER_API_KEY=

Start the services

docker compose up -d

Access the application

Profiles

Docker Compose profiles control which services start:

ProfileCommandServices Started
(default)docker compose up -dapi, m3-plugin-runner, traefik
productiondocker compose --profile production up -d+ nginx frontend
devdocker compose -f docker-compose.yml -f docker-compose.dev.yml --profile dev up -d+ Vite dev server with HMR

Development Mode

Development mode adds hot reload for all services:

# Start with dev overrides docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile dev up -d

What changes in dev mode:

ServiceProductionDevelopment
APIPre-built api.Dockerfileapi.dev.Dockerfile with source mount + pnpm dev
Frontendnginx serving static buildVite dev server at :5173 with HMR
Plugin RunnerStandard startupSource mount + uvicorn --reload

Production Mode

# Build and start all production services docker compose --profile production up -d --build

Architecture

Customizing Ports

Override default ports via environment variables in .env:

VariableDefaultService
API_PORT3500API server
FRONTEND_PORT80Frontend (nginx, production)
FRONTEND_DEV_PORT5173Frontend (Vite, development)
WEBAPP_RUNNER_PORT8090Plugin runner
TRAEFIK_HTTP_PORT8880Traefik HTTP
TRAEFIK_HTTPS_PORT8443Traefik HTTPS
TRAEFIK_DASHBOARD_PORT8881Traefik dashboard

Volumes

VolumePurpose
marie-plugin-dataPlugin container data and deployed applications

Development mode adds isolated node_modules volumes to avoid conflicts with host:

VolumePurpose
api-node-modulesAPI dependencies (dev only)
frontend-node-modulesRoot dependencies (dev only)
frontend-ui-node-modulesUI dependencies (dev only)

Scaling Services

Scale individual services with Docker Compose:

# Run 3 API server instances (requires load balancer) docker compose up -d --scale api=3

When scaling the API, configure Traefik or an external load balancer to distribute requests. The m3-plugin-runner should remain a single instance as it manages Docker containers on the host.

Commands Reference

CommandDescription
docker compose up -dStart core services
docker compose --profile production up -d --buildStart all services in production mode
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile dev up -dStart in development mode
docker compose downStop all services
docker compose logs -fView all logs
docker compose logs -f apiView API logs
docker compose logs -f m3-plugin-runnerView m3-plugin-runner logs
docker compose buildRebuild all images
docker compose psList running containers
docker compose down -vStop and remove all containers and volumes

Environment Variable Reference

VariableDefaultDescription
DATABASE_URL(required)PostgreSQL connection string
S3_ENDPOINT_URL(required)S3-compatible endpoint
S3_ACCESS_KEY_ID(required)S3 access key
S3_SECRET_ACCESS_KEY(required)S3 secret key
JWT_ACCESS_SECRET(required)JWT access token secret
JWT_REFRESH_SECRET(required)JWT refresh token secret
NODE_ENVproductionNode.js environment
S3_BUCKET_NAMEmarieS3 bucket name
S3_REGIONus-east-1S3 region
S3_ADDRESSING_STYLEpathS3 addressing style
FRONTEND_URLhttp://localhost:5173Frontend URL for CORS
API_URLhttp://localhost:5173API base URL
WEBAPP_DOMAINapps.localhostDomain for webapp subdomains
RUNNER_API_KEY(none)Plugin-runner authentication key
PUBLIC_BASE_URLhttp://localhost:8880Public base URL for webapps
TRAEFIK_LOG_LEVELINFOTraefik log verbosity
DEBUGfalseEnable debug mode

Production Considerations

HTTPS with Traefik

For production HTTPS, configure Traefik with Let’s Encrypt:

# Add to traefik command in docker-compose.yml - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true" - "--certificatesresolvers.letsencrypt.acme.email=your@email.com" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"

Database Backups

Regularly back up your PostgreSQL database:

pg_dump $DATABASE_URL > backup-$(date +%Y%m%d).sql

Resource Limits

Add resource limits to prevent any single service from consuming all available resources:

services: api: deploy: resources: limits: memory: 1G cpus: "1.0"

Troubleshooting

Database connection refused

# Verify DATABASE_URL is correct docker compose exec api node -e "console.log(process.env.DATABASE_URL)" # Test connection from inside the container docker compose exec api npx prisma db pull \ --schema packages/@marie/db/prisma/schema.prisma

Traefik not routing to webapps

  1. Check the Traefik dashboard at http://localhost:8881 
  2. Verify webapps are on the marie-network:
docker network inspect marie-network
  1. Check webapp container labels:
docker ps --filter "network=marie-network" --format "table {{.Names}}\t{{.Labels}}"

Frontend shows blank page

# Check frontend build docker compose logs frontend # Verify API is reachable curl http://localhost:3500/health

Plugin runner can’t create containers

# Verify Docker socket is mounted docker compose exec m3-plugin-runner ls -la /var/run/docker.sock # Check m3-plugin-runner logs docker compose logs m3-plugin-runner

Hot reload not working in dev mode

Ensure you’re using the dev compose override:

# Correct command docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile dev up -d # Verify source volumes are mounted docker compose exec api ls -la /app/packages
Last updated on