Skip to content

Commit 76e1770

Browse files
bcherryclaude
andauthored
feat: improve Dockerfile build performance with multi-stage builds (#789)
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 <noreply@anthropic.com>
1 parent 5b3df08 commit 76e1770

6 files changed

Lines changed: 239 additions & 120 deletions

File tree

pkg/agentfs/examples/node.Dockerfile

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# For more information on the build process, see https://docs.livekit.io/agents/ops/deployment/builds/
33
# syntax=docker/dockerfile:1
44

5-
# Use the official Node.js v22 base image with Node.js 22.10.0
5+
# Use the official Node.js v22 base image
66
# We use the slim variant to keep the image size smaller while still having essential tools
77
ARG NODE_VERSION=22
88
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
1919
# Pin pnpm version for reproducible builds
2020
RUN npm install -g pnpm@10
2121

22+
# --- Build stage ---
23+
# Install dependencies, build the project, and prepare production assets
24+
FROM base AS build
25+
2226
# Create a new directory for our application code
2327
# And set it as the working directory
2428
WORKDIR /app
@@ -30,7 +34,7 @@ COPY package.json pnpm-lock.yaml ./
3034
# --frozen-lockfile ensures we use exact versions from pnpm-lock.yaml for reproducible builds
3135
RUN pnpm install --frozen-lockfile
3236

33-
# Copy all remaining pplication files into the container
37+
# Copy all remaining application files into the container
3438
# This includes source code, configuration files, and dependency specifications
3539
# (Excludes files specified in .dockerignore)
3640
COPY . .
@@ -39,8 +43,20 @@ COPY . .
3943
# Your package.json must contain a "build" script, such as `"build": "tsc"`
4044
RUN pnpm build
4145

46+
# Pre-download any ML models or files the agent needs
47+
# This ensures the container is ready to run immediately without downloading
48+
# dependencies at runtime, which improves startup time and reliability
49+
# Your package.json must contain a "download-files" script, such as `"download-files": "pnpm run build && node dist/agent.js download-files"`
50+
RUN pnpm download-files
51+
52+
# Remove dev dependencies for a leaner production image
53+
RUN pnpm prune --prod
54+
55+
# --- Production stage ---
56+
FROM base
57+
4258
# Create a non-privileged user that the app will run under
43-
# See https://docs.docker.com/develop/develop-images/dockerfile_best_practices/#user
59+
# See https://docs.docker.com/build/building/best-practices/#user
4460
ARG UID=10001
4561
RUN adduser \
4662
--disabled-password \
@@ -50,19 +66,12 @@ RUN adduser \
5066
--uid "${UID}" \
5167
appuser
5268

53-
# Set proper permissions
54-
RUN chown -R appuser:appuser /app
55-
USER appuser
69+
WORKDIR /app
5670

57-
# Pre-download any ML models or files the agent needs
58-
# This ensures the container is ready to run immediately without downloading
59-
# dependencies at runtime, which improves startup time and reliability
60-
# Your package.json must contain a "download-files" script, such as `"download-files": "pnpm run build && node dist/agent.js download-files"`
61-
RUN pnpm download-files
71+
# Copy the built application with correct ownership in a single layer
72+
# This avoids expensive recursive chown operations on node_modules
73+
COPY --from=build --chown=appuser:appuser /app /app
6274

63-
# Switch back to root to remove dev dependencies and finalize setup
64-
USER root
65-
RUN pnpm prune --prod && chown -R appuser:appuser /app
6675
USER appuser
6776

6877
# Set Node.js to production mode
@@ -71,4 +80,4 @@ ENV NODE_ENV=production
7180
# Run the application
7281
# The "start" command tells the worker to connect to LiveKit and begin waiting for jobs.
7382
# Your package.json must contain a "start" script, such as `"start": "node dist/agent.js start"`
74-
CMD [ "pnpm", "start" ]
83+
CMD [ "pnpm", "start" ]
Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
1+
# Project tests
2+
**/*.test.ts
3+
**/*.spec.ts
4+
__tests__/
5+
tests/
6+
evals/
7+
18
# Node.js dependencies
2-
**/node_modules
3-
**/npm-debug.log
4-
**/yarn-error.log
5-
**/pnpm-debug.log
9+
node_modules
10+
npm-debug.log
11+
yarn-error.log
12+
pnpm-debug.log
613

714
# Build outputs
8-
**/dist
9-
**/build
10-
**/coverage
15+
dist/
16+
build/
17+
coverage/
1118

1219
# Local environment & config files
13-
**/.env
14-
**/.env.local
20+
.env
21+
.env.local
1522
.DS_Store
1623

1724
# Logs & temp files
18-
**/*.log
19-
**/*.gz
20-
**/*.tgz
21-
**/.tmp
22-
**/.cache
25+
*.log
26+
*.gz
27+
*.tgz
28+
.tmp
29+
.cache
2330

2431
# Docker artifacts
2532
Dockerfile*
@@ -31,3 +38,34 @@ Dockerfile*
3138
.idea
3239
.vscode
3340

41+
# Test artifacts
42+
coverage/
43+
44+
# Project docs and misc
45+
README.md
46+
CONTRIBUTING.md
47+
LICENSE
48+
49+
# Coding agent files
50+
.claude/
51+
.codex/
52+
.cursor/
53+
.windsurf/
54+
.gemini/
55+
.cline/
56+
.clinerules
57+
.clinerules/
58+
.aider*
59+
.cursorrules
60+
.cursorignore
61+
.cursorindexingignore
62+
.clineignore
63+
.codeiumignore
64+
.geminiignore
65+
.windsurfrules
66+
CLAUDE.md
67+
AGENTS.md
68+
GEMINI.md
69+
.github/copilot-instructions.md
70+
.github/personal-instructions.md
71+
.github/instructions/

pkg/agentfs/examples/python.pip.Dockerfile

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,9 @@ ENV PYTHONUNBUFFERED=1
1414
# Disable pip version check to speed up builds
1515
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
1616

17-
# Create a non-privileged user that the app will run under.
18-
# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
19-
ARG UID=10001
20-
RUN adduser \
21-
--disabled-password \
22-
--gecos "" \
23-
--home "/app" \
24-
--shell "/sbin/nologin" \
25-
--uid "${UID}" \
26-
appuser
17+
# --- Build stage ---
18+
# Install dependencies, build native extensions, and prepare the application
19+
FROM base AS build
2720

2821
# Install build dependencies required for Python packages with native extensions
2922
# gcc: C compiler needed for building Python packages with C extensions
@@ -43,28 +36,50 @@ WORKDIR /app
4336
# Copy just the dependency files first, for more efficient layer caching
4437
COPY requirements.txt ./
4538

46-
# Install Python dependencies using pip
47-
# --no-cache-dir ensures we don't use the system cache
39+
# Create a virtual environment and install Python dependencies
40+
# The venv keeps dependencies in /app so they can be copied to the production stage
41+
RUN python -m venv .venv
42+
ENV PATH="/app/.venv/bin:$PATH"
4843
RUN pip install --no-cache-dir -r requirements.txt
4944

50-
# Copy all remaining pplication files into the container
45+
# Copy all remaining application files into the container
5146
# This includes source code, configuration files, and dependency specifications
5247
# (Excludes files specified in .dockerignore)
5348
COPY . .
5449

55-
# Change ownership of all app files to the non-privileged user
56-
# This ensures the application can read/write files as needed
57-
RUN chown -R appuser:appuser /app
58-
59-
# Switch to the non-privileged user for all subsequent operations
60-
# This improves security by not running as root
61-
USER appuser
62-
6350
# Pre-download any ML models or files the agent needs
6451
# This ensures the container is ready to run immediately without downloading
6552
# dependencies at runtime, which improves startup time and reliability
6653
RUN python "{{.ProgramMain}}" download-files
6754

55+
# --- Production stage ---
56+
# Build tools (gcc, g++, python3-dev) are not included in the final image
57+
FROM base
58+
59+
# Create a non-privileged user that the app will run under.
60+
# See https://docs.docker.com/build/building/best-practices/#user
61+
ARG UID=10001
62+
RUN adduser \
63+
--disabled-password \
64+
--gecos "" \
65+
--home "/app" \
66+
--shell "/sbin/nologin" \
67+
--uid "${UID}" \
68+
appuser
69+
70+
WORKDIR /app
71+
72+
# Copy the application and virtual environment with correct ownership in a single layer
73+
# This avoids expensive recursive chown and excludes build tools from the final image
74+
COPY --from=build --chown=appuser:appuser /app /app
75+
76+
# Activate virtual environment
77+
ENV PATH="/app/.venv/bin:$PATH"
78+
79+
# Switch to the non-privileged user for all subsequent operations
80+
# This improves security by not running as root
81+
USER appuser
82+
6883
# Run the application
6984
# The "start" command tells the worker to connect to LiveKit and begin waiting for jobs.
7085
CMD ["python", "{{.ProgramMain}}", "start"]
Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
1+
# Project tests
2+
test/
3+
tests/
4+
eval/
5+
evals/
6+
17
# Python bytecode and artifacts
2-
**/__pycache__/
3-
**/*.py[cod]
4-
**/*.pyo
5-
**/*.pyd
6-
**/*.egg-info/
7-
**/dist/
8-
**/build/
8+
__pycache__/
9+
*.py[cod]
10+
*.pyo
11+
*.pyd
12+
*.egg-info/
13+
dist/
14+
build/
915

1016
# Virtual environments
11-
**/.venv/
12-
**/venv/
17+
.venv/
18+
venv/
1319

1420
# Caches and test output
15-
**/.cache/
16-
**/.pytest_cache/
17-
**/.ruff_cache/
18-
**/coverage/
21+
.cache/
22+
.pytest_cache/
23+
.ruff_cache/
24+
coverage/
1925

2026
# Logs and temp files
21-
**/*.log
22-
**/*.gz
23-
**/*.tgz
24-
**/.tmp
25-
**/.cache
27+
*.log
28+
*.gz
29+
*.tgz
30+
.tmp
31+
.cache
2632

2733
# Environment variables
28-
**/.env
29-
**/.env.*
34+
.env
35+
.env.*
3036

3137
# VCS, editor, OS
3238
.git
@@ -39,10 +45,29 @@
3945

4046
# Project docs and misc
4147
README.md
48+
CONTRIBUTING.md
4249
LICENSE
4350

44-
# Project tests
45-
test/
46-
tests/
47-
eval/
48-
evals/
51+
# Coding agent files
52+
.claude/
53+
.codex/
54+
.cursor/
55+
.windsurf/
56+
.gemini/
57+
.cline/
58+
.clinerules
59+
.clinerules/
60+
.aider*
61+
.cursorrules
62+
.cursorignore
63+
.cursorindexingignore
64+
.clineignore
65+
.codeiumignore
66+
.geminiignore
67+
.windsurfrules
68+
CLAUDE.md
69+
AGENTS.md
70+
GEMINI.md
71+
.github/copilot-instructions.md
72+
.github/personal-instructions.md
73+
.github/instructions/

0 commit comments

Comments
 (0)