From c8ada29df4538dc93af9f950501f3e5cf363b5ce Mon Sep 17 00:00:00 2001 From: Ben Cherry Date: Tue, 10 Mar 2026 22:27:52 -0700 Subject: [PATCH] feat: improve Dockerfile build performance with multi-stage builds Convert all Dockerfile templates to multi-stage builds to eliminate expensive `chown -R` operations. Use `COPY --from=build --chown=` to set ownership in a single layer instead. Add venv to pip template for clean multi-stage copying. Add coding agent files and test patterns to dockerignore templates. Matches livekit/web#3448. Co-Authored-By: Claude Opus 4.6 --- pkg/agentfs/examples/node.Dockerfile | 39 ++++++---- pkg/agentfs/examples/node.dockerignore | 66 +++++++++++++---- pkg/agentfs/examples/python.pip.Dockerfile | 57 +++++++++------ pkg/agentfs/examples/python.pip.dockerignore | 75 +++++++++++++------- pkg/agentfs/examples/python.uv.Dockerfile | 48 ++++++++----- pkg/agentfs/examples/python.uv.dockerignore | 75 +++++++++++++------- 6 files changed, 241 insertions(+), 119 deletions(-) diff --git a/pkg/agentfs/examples/node.Dockerfile b/pkg/agentfs/examples/node.Dockerfile index ec6809d0..652bca00 100644 --- a/pkg/agentfs/examples/node.Dockerfile +++ b/pkg/agentfs/examples/node.Dockerfile @@ -2,7 +2,7 @@ # For more information on the build process, see https://docs.livekit.io/agents/ops/deployment/builds/ # syntax=docker/dockerfile:1 -# Use the official Node.js v22 base image with Node.js 22.10.0 +# Use the official Node.js v22 base image # We use the slim variant to keep the image size smaller while still having essential tools ARG NODE_VERSION=22 FROM node:${NODE_VERSION}-slim AS base @@ -19,6 +19,10 @@ RUN apt-get update -qq && apt-get install --no-install-recommends -y ca-certific # Pin pnpm version for reproducible builds RUN npm install -g pnpm@10 +# --- Build stage --- +# Install dependencies, build the project, and prepare production assets +FROM base AS build + # Create a new directory for our application code # And set it as the working directory WORKDIR /app @@ -30,7 +34,7 @@ COPY package.json pnpm-lock.yaml ./ # --frozen-lockfile ensures we use exact versions from pnpm-lock.yaml for reproducible builds RUN pnpm install --frozen-lockfile -# Copy all remaining pplication files into the container +# Copy all remaining application files into the container # This includes source code, configuration files, and dependency specifications # (Excludes files specified in .dockerignore) COPY . . @@ -39,8 +43,20 @@ COPY . . # Your package.json must contain a "build" script, such as `"build": "tsc"` RUN pnpm build +# Pre-download any ML models or files the agent needs +# This ensures the container is ready to run immediately without downloading +# dependencies at runtime, which improves startup time and reliability +# Your package.json must contain a "download-files" script, such as `"download-files": "pnpm run build && node dist/agent.js download-files"` +RUN pnpm download-files + +# Remove dev dependencies for a leaner production image +RUN pnpm prune --prod + +# --- Production stage --- +FROM base + # Create a non-privileged user that the app will run under -# See https://docs.docker.com/develop/develop-images/dockerfile_best_practices/#user +# See https://docs.docker.com/build/building/best-practices/#user ARG UID=10001 RUN adduser \ --disabled-password \ @@ -50,19 +66,12 @@ RUN adduser \ --uid "${UID}" \ appuser -# Set proper permissions -RUN chown -R appuser:appuser /app -USER appuser +WORKDIR /app -# Pre-download any ML models or files the agent needs -# This ensures the container is ready to run immediately without downloading -# dependencies at runtime, which improves startup time and reliability -# Your package.json must contain a "download-files" script, such as `"download-files": "pnpm run build && node dist/agent.js download-files"` -RUN pnpm download-files +# Copy the built application with correct ownership in a single layer +# This avoids expensive recursive chown operations on node_modules +COPY --from=build --chown=appuser:appuser /app /app -# Switch back to root to remove dev dependencies and finalize setup -USER root -RUN pnpm prune --prod && chown -R appuser:appuser /app USER appuser # Set Node.js to production mode @@ -71,4 +80,4 @@ ENV NODE_ENV=production # Run the application # The "start" command tells the worker to connect to LiveKit and begin waiting for jobs. # Your package.json must contain a "start" script, such as `"start": "node dist/agent.js start"` -CMD [ "pnpm", "start" ] \ No newline at end of file +CMD [ "pnpm", "start" ] diff --git a/pkg/agentfs/examples/node.dockerignore b/pkg/agentfs/examples/node.dockerignore index b971e256..434439bd 100644 --- a/pkg/agentfs/examples/node.dockerignore +++ b/pkg/agentfs/examples/node.dockerignore @@ -1,25 +1,32 @@ +# Project tests +**/*.test.ts +**/*.spec.ts +__tests__/ +tests/ +evals/ + # Node.js dependencies -**/node_modules -**/npm-debug.log -**/yarn-error.log -**/pnpm-debug.log +node_modules +npm-debug.log +yarn-error.log +pnpm-debug.log # Build outputs -**/dist -**/build -**/coverage +dist/ +build/ +coverage/ # Local environment & config files -**/.env -**/.env.local +.env +.env.local .DS_Store # Logs & temp files -**/*.log -**/*.gz -**/*.tgz -**/.tmp -**/.cache +*.log +*.gz +*.tgz +.tmp +.cache # Docker artifacts Dockerfile* @@ -31,3 +38,34 @@ Dockerfile* .idea .vscode +# Test artifacts +coverage/ + +# Project docs and misc +README.md +CONTRIBUTING.md +LICENSE + +# Coding agent files +.claude/ +.codex/ +.cursor/ +.windsurf/ +.gemini/ +.cline/ +.clinerules +.clinerules/ +.aider* +.cursorrules +.cursorignore +.cursorindexingignore +.clineignore +.codeiumignore +.geminiignore +.windsurfrules +CLAUDE.md +AGENTS.md +GEMINI.md +.github/copilot-instructions.md +.github/personal-instructions.md +.github/instructions/ diff --git a/pkg/agentfs/examples/python.pip.Dockerfile b/pkg/agentfs/examples/python.pip.Dockerfile index 1ef70a65..129d3b8d 100644 --- a/pkg/agentfs/examples/python.pip.Dockerfile +++ b/pkg/agentfs/examples/python.pip.Dockerfile @@ -14,16 +14,9 @@ ENV PYTHONUNBUFFERED=1 # Disable pip version check to speed up builds ENV PIP_DISABLE_PIP_VERSION_CHECK=1 -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/app" \ - --shell "/sbin/nologin" \ - --uid "${UID}" \ - appuser +# --- Build stage --- +# Install dependencies, build native extensions, and prepare the application +FROM base AS build # Install build dependencies required for Python packages with native extensions # gcc: C compiler needed for building Python packages with C extensions @@ -43,28 +36,50 @@ WORKDIR /app # Copy just the dependency files first, for more efficient layer caching COPY requirements.txt ./ -# Install Python dependencies using pip -# --no-cache-dir ensures we don't use the system cache +# Create a virtual environment and install Python dependencies +# The venv keeps dependencies in /app so they can be copied to the production stage +RUN python -m venv .venv +ENV PATH="/app/.venv/bin:$PATH" RUN pip install --no-cache-dir -r requirements.txt -# Copy all remaining pplication files into the container +# Copy all remaining application files into the container # This includes source code, configuration files, and dependency specifications # (Excludes files specified in .dockerignore) COPY . . -# Change ownership of all app files to the non-privileged user -# This ensures the application can read/write files as needed -RUN chown -R appuser:appuser /app - -# Switch to the non-privileged user for all subsequent operations -# This improves security by not running as root -USER appuser - # Pre-download any ML models or files the agent needs # This ensures the container is ready to run immediately without downloading # dependencies at runtime, which improves startup time and reliability RUN python "{{.ProgramMain}}" download-files +# --- Production stage --- +# Build tools (gcc, g++, python3-dev) are not included in the final image +FROM base + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/build/building/best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/app" \ + --shell "/sbin/nologin" \ + --uid "${UID}" \ + appuser + +WORKDIR /app + +# Copy the application and virtual environment with correct ownership in a single layer +# This avoids expensive recursive chown and excludes build tools from the final image +COPY --from=build --chown=appuser:appuser /app /app + +# Activate virtual environment +ENV PATH="/app/.venv/bin:$PATH" + +# Switch to the non-privileged user for all subsequent operations +# This improves security by not running as root +USER appuser + # Run the application # The "start" command tells the worker to connect to LiveKit and begin waiting for jobs. CMD ["python", "{{.ProgramMain}}", "start"] diff --git a/pkg/agentfs/examples/python.pip.dockerignore b/pkg/agentfs/examples/python.pip.dockerignore index 828308c5..27fb03dd 100644 --- a/pkg/agentfs/examples/python.pip.dockerignore +++ b/pkg/agentfs/examples/python.pip.dockerignore @@ -1,32 +1,38 @@ +# Project tests +test/ +tests/ +eval/ +evals/ + # Python bytecode and artifacts -**/__pycache__/ -**/*.py[cod] -**/*.pyo -**/*.pyd -**/*.egg-info/ -**/dist/ -**/build/ +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.egg-info/ +dist/ +build/ # Virtual environments -**/.venv/ -**/venv/ +.venv/ +venv/ # Caches and test output -**/.cache/ -**/.pytest_cache/ -**/.ruff_cache/ -**/coverage/ +.cache/ +.pytest_cache/ +.ruff_cache/ +coverage/ # Logs and temp files -**/*.log -**/*.gz -**/*.tgz -**/.tmp -**/.cache +*.log +*.gz +*.tgz +.tmp +.cache # Environment variables -**/.env -**/.env.* +.env +.env.* # VCS, editor, OS .git @@ -39,10 +45,29 @@ # Project docs and misc README.md +CONTRIBUTING.md LICENSE -# Project tests -test/ -tests/ -eval/ -evals/ +# Coding agent files +.claude/ +.codex/ +.cursor/ +.windsurf/ +.gemini/ +.cline/ +.clinerules +.clinerules/ +.aider* +.cursorrules +.cursorignore +.cursorindexingignore +.clineignore +.codeiumignore +.geminiignore +.windsurfrules +CLAUDE.md +AGENTS.md +GEMINI.md +.github/copilot-instructions.md +.github/personal-instructions.md +.github/instructions/ diff --git a/pkg/agentfs/examples/python.uv.Dockerfile b/pkg/agentfs/examples/python.uv.Dockerfile index 19ec7374..ef85b085 100644 --- a/pkg/agentfs/examples/python.uv.Dockerfile +++ b/pkg/agentfs/examples/python.uv.Dockerfile @@ -12,16 +12,9 @@ FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-bookworm-slim AS base # the application crashes without emitting any logs due to buffering. ENV PYTHONUNBUFFERED=1 -# Create a non-privileged user that the app will run under. -# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/app" \ - --shell "/sbin/nologin" \ - --uid "${UID}" \ - appuser +# --- Build stage --- +# Install dependencies, build native extensions, and prepare the application +FROM base AS build # Install build dependencies required for Python packages with native extensions # gcc: C compiler needed for building Python packages with C extensions @@ -48,24 +41,41 @@ RUN mkdir -p src # Ensure your uv.lock file is checked in for consistency across environments RUN uv sync --locked -# Copy all remaining pplication files into the container +# Copy all remaining application files into the container # This includes source code, configuration files, and dependency specifications # (Excludes files specified in .dockerignore) COPY . . -# Change ownership of all app files to the non-privileged user -# This ensures the application can read/write files as needed -RUN chown -R appuser:appuser /app - -# Switch to the non-privileged user for all subsequent operations -# This improves security by not running as root -USER appuser - # Pre-download any ML models or files the agent needs # This ensures the container is ready to run immediately without downloading # dependencies at runtime, which improves startup time and reliability RUN uv run "{{.ProgramMain}}" download-files +# --- Production stage --- +# Build tools (gcc, g++, python3-dev) are not included in the final image +FROM base + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/build/building/best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/app" \ + --shell "/sbin/nologin" \ + --uid "${UID}" \ + appuser + +WORKDIR /app + +# Copy the application and virtual environment with correct ownership in a single layer +# This avoids expensive recursive chown and excludes build tools from the final image +COPY --from=build --chown=appuser:appuser /app /app + +# Switch to the non-privileged user for all subsequent operations +# This improves security by not running as root +USER appuser + # Run the application using UV # UV will activate the virtual environment and run the agent. # The "start" command tells the worker to connect to LiveKit and begin waiting for jobs. diff --git a/pkg/agentfs/examples/python.uv.dockerignore b/pkg/agentfs/examples/python.uv.dockerignore index 828308c5..27fb03dd 100644 --- a/pkg/agentfs/examples/python.uv.dockerignore +++ b/pkg/agentfs/examples/python.uv.dockerignore @@ -1,32 +1,38 @@ +# Project tests +test/ +tests/ +eval/ +evals/ + # Python bytecode and artifacts -**/__pycache__/ -**/*.py[cod] -**/*.pyo -**/*.pyd -**/*.egg-info/ -**/dist/ -**/build/ +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.egg-info/ +dist/ +build/ # Virtual environments -**/.venv/ -**/venv/ +.venv/ +venv/ # Caches and test output -**/.cache/ -**/.pytest_cache/ -**/.ruff_cache/ -**/coverage/ +.cache/ +.pytest_cache/ +.ruff_cache/ +coverage/ # Logs and temp files -**/*.log -**/*.gz -**/*.tgz -**/.tmp -**/.cache +*.log +*.gz +*.tgz +.tmp +.cache # Environment variables -**/.env -**/.env.* +.env +.env.* # VCS, editor, OS .git @@ -39,10 +45,29 @@ # Project docs and misc README.md +CONTRIBUTING.md LICENSE -# Project tests -test/ -tests/ -eval/ -evals/ +# Coding agent files +.claude/ +.codex/ +.cursor/ +.windsurf/ +.gemini/ +.cline/ +.clinerules +.clinerules/ +.aider* +.cursorrules +.cursorignore +.cursorindexingignore +.clineignore +.codeiumignore +.geminiignore +.windsurfrules +CLAUDE.md +AGENTS.md +GEMINI.md +.github/copilot-instructions.md +.github/personal-instructions.md +.github/instructions/