Skip to content

Docker Architecture

protoLabs uses a multi-stage Dockerfile to build both server and UI images from a single file.

Files

FilePurpose
DockerfileProduction multi-stage build
Dockerfile.devDevelopment build with live reload
docker-entrypoint.shContainer initialization script

Multi-Stage Build

The production Dockerfile defines multiple stages:

┌─────────────────────────────────────────────────────────────┐
│                        Dockerfile                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┐                                               │
│  │   base   │  Common setup: Node 22, build tools           │
│  └────┬─────┘                                               │
│       │                                                      │
│  ┌────┴──────────────┬──────────────────┬──────────────┐    │
│  │                   │                  │              │    │
│  ▼                   ▼                  ▼              │    │
│  ┌──────────────┐   ┌──────────────┐   ┌────────────┐ │    │
│  │server-builder│   │  ui-builder  │   │docs-builder│ │    │
│  │  Build libs  │   │  Build libs  │   │ VitePress  │ │    │
│  │ Build server │   │   Build UI   │   │ Build docs │ │    │
│  └──────┬───────┘   └──────┬───────┘   └─────┬──────┘ │    │
│         │                  │                 │        │    │
│         ▼                  ▼                 ▼        │    │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────┐  │    │
│  │    server    │   │      ui      │   │   docs   │  │    │
│  │  Node.js     │   │    nginx     │   │  nginx   │  │    │
│  │  Production  │   │   Static     │   │ Static   │  │    │
│  └──────────────┘   └──────────────┘   └──────────┘  │    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Build Targets

Server Target

bash
docker build --target server -t automaker-server .

Base image: node:22-slim

Includes:

  • Node.js runtime
  • Git, curl, bash, gosu
  • GitHub CLI (gh)
  • Claude CLI (@anthropic-ai/claude-code)
  • Cursor CLI (cursor-agent)
  • OpenCode CLI
  • Playwright browser dependencies

Build args:

  • UID / GID - Match container user to host user (default: 1001)
  • GIT_COMMIT_SHA - Track build source (for labels)

UI Target

bash
docker build --target ui -t automaker-ui .

Base image: nginx:alpine

Includes:

  • Pre-built static files
  • nginx configuration for SPA routing

Build args:

  • VITE_SERVER_URL - API server URL (empty = relative URLs)
  • GIT_COMMIT_SHA - Track build source

Docs Target

bash
docker build --target docs -t automaker-docs .

Base image: nginx:alpine

Includes:

  • Pre-built VitePress static site from docs/
  • nginx configuration for SPA routing

Build process:

  1. Installs vitepress standalone (avoids Vite version conflict with the monorepo)
  2. Runs vitepress build docs to generate static HTML
  3. Copies output to nginx serving directory

Base Stage

The base stage is shared by both server and UI builders:

dockerfile
FROM node:22-slim AS base

# Install build dependencies for native modules (node-pty)
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 make g++ \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy package files for npm workspace
COPY package*.json ./
COPY libs/*/package*.json ./libs/*/
COPY scripts ./scripts

Server Production Stage

Key features of the server production image:

Non-Root User

dockerfile
ARG UID=1001
ARG GID=1001

RUN groupadd -o -g ${GID} automaker && \
    useradd -o -u ${UID} -g automaker -m -d /home/automaker -s /bin/bash automaker

The -o flag allows non-unique IDs (GID 1000 may already exist as 'node' group).

CLI Tools

dockerfile
# Claude CLI (global npm package)
RUN npm install -g @anthropic-ai/claude-code

# Cursor CLI (user-local installation)
USER automaker
RUN curl https://cursor.com/install -fsS | bash
USER root

# OpenCode CLI
RUN curl -fsSL https://opencode.ai/install | bash

Git Configuration

dockerfile
# Safe directory for mounted volumes
RUN git config --system --add safe.directory '*' && \
    # Use gh as credential helper (works with GH_TOKEN)
    git config --system credential.helper '!gh auth git-credential'

Health Check

dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:3008/api/health || exit 1

Development Dockerfile

Dockerfile.dev is a simpler single-stage build for development:

dockerfile
FROM node:22-slim

# Build tools + runtime tools
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 make g++ \
    git curl bash gosu ca-certificates openssh-client \
    && rm -rf /var/lib/apt/lists/*

# Same CLI installations as production...

WORKDIR /app
EXPOSE 3007 3008

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["npm", "run", "dev:web"]

Key differences from production:

  • Single stage (no build optimization)
  • Both ports exposed (3007 + 3008)
  • Source code mounted as volume, not copied
  • Designed for docker-compose.dev.yml

Entrypoint Script

docker-entrypoint.sh runs as root before switching to the automaker user:

bash
#!/bin/sh
set -e

# Create CLI config directories
mkdir -p /home/automaker/.claude
mkdir -p /home/automaker/.cursor
mkdir -p /home/automaker/.local/share/opencode
mkdir -p /home/automaker/.config/opencode
mkdir -p /home/automaker/.cache/opencode
mkdir -p /home/automaker/.npm

# Write OAuth credentials from environment variables
if [ -n "$CLAUDE_OAUTH_CREDENTIALS" ]; then
    echo "$CLAUDE_OAUTH_CREDENTIALS" > /home/automaker/.claude/.credentials.json
    chmod 600 /home/automaker/.claude/.credentials.json
fi

if [ -n "$CURSOR_AUTH_TOKEN" ]; then
    mkdir -p /home/automaker/.config/cursor
    echo "{\"accessToken\": \"$CURSOR_AUTH_TOKEN\"}" > /home/automaker/.config/cursor/auth.json
    chmod 600 /home/automaker/.config/cursor/auth.json
fi

# Fix permissions
chown -R automaker:automaker /home/automaker/.claude
chown -R automaker:automaker /home/automaker/.cursor
# ... other directories

# Switch to automaker user and run command
exec gosu automaker "$@"

Building Images

Build for Production

bash
# Build both images
docker compose build

# Build with custom UID/GID
UID=$(id -u) GID=$(id -g) docker compose build

# Build with commit SHA label
docker build --target server \
  --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) \
  -t automaker-server .

Build for Development

bash
docker compose -f docker-compose.dev.yml build

Image Labels

The production images include a label for tracking the source commit:

dockerfile
ARG GIT_COMMIT_SHA=unknown
LABEL automaker.git.commit.sha="${GIT_COMMIT_SHA}"

Query the label:

bash
docker inspect automaker-server --format '{{ index .Config.Labels "automaker.git.commit.sha" }}'

Platform Support

Both Dockerfiles support multi-architecture builds:

bash
# AMD64 (x86_64)
docker build --platform linux/amd64 --target server -t automaker-server:amd64 .

# ARM64 (Apple Silicon, ARM servers)
docker build --platform linux/arm64 --target server -t automaker-server:arm64 .

The GitHub CLI installation handles architecture detection:

dockerfile
ARCH=$(uname -m)
case "$ARCH" in
    x86_64) GH_ARCH="amd64" ;;
    aarch64|arm64) GH_ARCH="arm64" ;;
    *) echo "Unsupported architecture: $ARCH" && exit 1 ;;
esac

Built by protoLabs — Open source on GitHub