Skip to content

Commit 7edbfa9

Browse files
Implement Docker layer caching for all workflows (#176)
* Add Docker layer caching to PR workflow - Use docker/build-push-action with GHA cache backend - Implement PR-specific cache scope with fallback to main branch - Enable mode=max to cache all Dockerfile stages - Expected improvement: 60s → 10-20s on warm builds * Add Docker layer caching to release workflow - Use docker/build-push-action with GHA cache backend for both beta and stable releases - Implement shared 'release' cache scope for main branch builds - Enable mode=max to cache all Dockerfile stages including multi-arch layers - Expected improvement: 7.5min → 2min on warm builds (70% reduction) * Add Docker layer caching to preview package workflow - Use docker/build-push-action with GHA cache backend - Implement preview-specific cache scope with multi-level fallback - Reuse PR cache from pullrequest.yml when available - Enable mode=max to cache all Dockerfile stages - Expected improvement: 7.5min → 2-3min on warm builds * Add cache mounts for package managers - Add npm cache mount in builder stage for faster dependency installs - Add apt cache mounts in runtime stage for faster system package installs - Add pip cache mount in runtime stage for faster Python package installs - Removes --no-cache-dir from pip to enable caching - Additional ~20% speedup when dependencies change * Add cache mount to prod-deps stage Adds npm cache mount to prod-deps stage for consistency with builder stage. This speeds up production dependency installs when layers are invalidated. * Improve workflow configuration - Remove leftover instructional comment in pullrequest.yml - Centralize version extraction in release.yml unit-tests job - Reuse version output in publish jobs to eliminate duplication * Add changeset for Dockerfile cache mounts * Increase pkg-pr-new timeout to 15 minutes Multi-arch Docker builds take 7-8 minutes on cold cache. The previous 10-minute timeout was too tight and caused failures when builds took slightly longer due to runner variance or network conditions. 15 minutes provides adequate 2x buffer while still catching genuinely stuck builds.
1 parent 1bf3576 commit 7edbfa9

File tree

5 files changed

+92
-15
lines changed

5 files changed

+92
-15
lines changed

.changeset/docker-layer-caching.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@cloudflare/sandbox': patch
3+
---
4+
5+
Add cache mounts to Dockerfile for faster builds
6+
7+
Adds cache mounts for npm, apt, and pip package managers in the Dockerfile. This speeds up Docker image builds when dependencies change, particularly beneficial for users building from source.

.github/workflows/pkg-pr-new.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515
jobs:
1616
publish-preview:
1717
runs-on: ubuntu-latest
18-
timeout-minutes: 10
18+
timeout-minutes: 15
1919

2020
steps:
2121
- name: Checkout code
@@ -73,7 +73,20 @@ jobs:
7373
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
7474

7575
- name: Build and push Docker image (preview)
76-
run: npm run docker:publish --workspace=@cloudflare/sandbox
76+
uses: docker/build-push-action@v6
77+
with:
78+
context: .
79+
file: packages/sandbox/Dockerfile
80+
platforms: linux/amd64,linux/arm64
81+
push: true
82+
tags: cloudflare/sandbox:${{ steps.package-version.outputs.version }}
83+
cache-from: |
84+
type=gha,scope=preview-pr-${{ github.event.pull_request.number }}
85+
type=gha,scope=pr-${{ github.event.pull_request.number }}
86+
type=gha,scope=release
87+
cache-to: type=gha,mode=max,scope=preview-pr-${{ github.event.pull_request.number }}
88+
build-args: |
89+
SANDBOX_VERSION=${{ steps.package-version.outputs.version }}
7790
7891
- name: Publish to pkg.pr.new
7992
run: npx pkg-pr-new publish './packages/sandbox'

.github/workflows/pullrequest.yml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
unit-tests:
1616
timeout-minutes: 10
1717
runs-on: ubuntu-latest
18+
outputs:
19+
version: ${{ steps.get-version.outputs.version }}
1820
steps:
1921
- uses: actions/checkout@v4
2022

@@ -33,6 +35,12 @@ jobs:
3335
- name: Build packages
3436
run: npm run build
3537

38+
- name: Get package version
39+
id: get-version
40+
run: |
41+
VERSION=$(node -p "require('./packages/sandbox/package.json').version")
42+
echo "version=$VERSION" >> $GITHUB_OUTPUT
43+
3644
- name: Run sandbox unit tests
3745
run: |
3846
set +e
@@ -64,6 +72,7 @@ jobs:
6472

6573
# E2E tests against deployed worker
6674
e2e-tests:
75+
needs: unit-tests
6776
timeout-minutes: 30
6877
runs-on: ubuntu-latest
6978
steps:
@@ -102,9 +111,23 @@ jobs:
102111
cd tests/e2e/test-worker
103112
./generate-config.sh ${{ steps.env-name.outputs.worker_name }}
104113
105-
# Build Docker image for test worker (needed before deployment)
114+
# Build Docker image for test worker with caching
115+
- name: Set up Docker Buildx
116+
uses: docker/setup-buildx-action@v3
117+
106118
- name: Build test worker Docker image
107-
run: npm run docker:local -w @cloudflare/sandbox
119+
uses: docker/build-push-action@v6
120+
with:
121+
context: .
122+
file: packages/sandbox/Dockerfile
123+
load: true # Load into Docker daemon for local testing
124+
tags: cloudflare/sandbox-test:${{ needs.unit-tests.outputs.version || '0.0.0' }}
125+
cache-from: |
126+
type=gha,scope=pr-${{ github.event.pull_request.number }}
127+
type=gha,scope=release
128+
cache-to: type=gha,mode=max,scope=pr-${{ github.event.pull_request.number }}
129+
build-args: |
130+
SANDBOX_VERSION=${{ needs.unit-tests.outputs.version || '0.0.0' }}
108131
109132
# Deploy test worker using official Cloudflare action
110133
- name: Deploy test worker

.github/workflows/release.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
if: ${{ github.repository_owner == 'cloudflare' }}
1616
runs-on: ubuntu-latest
1717
timeout-minutes: 10
18+
outputs:
19+
version: ${{ steps.get-version.outputs.version }}
1820

1921
steps:
2022
- uses: actions/checkout@v4
@@ -34,6 +36,12 @@ jobs:
3436
- name: Build packages
3537
run: npm run build
3638

39+
- name: Get package version
40+
id: get-version
41+
run: |
42+
VERSION=$(node -p "require('./packages/sandbox/package.json').version")
43+
echo "version=$VERSION" >> $GITHUB_OUTPUT
44+
3745
- name: Run sandbox unit tests
3846
run: |
3947
set +e
@@ -106,7 +114,17 @@ jobs:
106114
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
107115

108116
- name: Build and push Docker image (beta)
109-
run: npm run docker:publish:beta --workspace=@cloudflare/sandbox
117+
uses: docker/build-push-action@v6
118+
with:
119+
context: .
120+
file: packages/sandbox/Dockerfile
121+
platforms: linux/amd64,linux/arm64
122+
push: true
123+
tags: cloudflare/sandbox:${{ needs.unit-tests.outputs.version }}-beta
124+
cache-from: type=gha,scope=release
125+
cache-to: type=gha,mode=max,scope=release
126+
build-args: |
127+
SANDBOX_VERSION=${{ needs.unit-tests.outputs.version }}
110128
111129
- name: Publish to npm with beta tag
112130
run: npm publish --tag beta --access public
@@ -153,7 +171,17 @@ jobs:
153171
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
154172

155173
- name: Build and push Docker image
156-
run: npm run docker:publish --workspace=@cloudflare/sandbox
174+
uses: docker/build-push-action@v6
175+
with:
176+
context: .
177+
file: packages/sandbox/Dockerfile
178+
platforms: linux/amd64,linux/arm64
179+
push: true
180+
tags: cloudflare/sandbox:${{ needs.unit-tests.outputs.version }}
181+
cache-from: type=gha,scope=release
182+
cache-to: type=gha,mode=max,scope=release
183+
build-args: |
184+
SANDBOX_VERSION=${{ needs.unit-tests.outputs.version }}
157185
158186
- id: changesets
159187
uses: changesets/action@v1

packages/sandbox/Dockerfile

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ WORKDIR /app
2929
COPY --from=pruner /app/out/json/ .
3030
COPY --from=pruner /app/out/package-lock.json ./package-lock.json
3131

32-
# Install ALL dependencies (including devDependencies for build)
33-
RUN npm ci
32+
# Install ALL dependencies with cache mount for npm packages
33+
RUN --mount=type=cache,target=/root/.npm \
34+
npm ci
3435

3536
# Copy pruned source code
3637
COPY --from=pruner /app/out/full/ .
@@ -53,7 +54,8 @@ COPY --from=builder /app/packages ./packages
5354
COPY --from=builder /app/tooling ./tooling
5455

5556
# Install ONLY production dependencies (excludes typescript, @types/*, etc.)
56-
RUN npm ci --production
57+
RUN --mount=type=cache,target=/root/.npm \
58+
npm ci --production
5759

5860
# ============================================================================
5961
# Stage 4: Runtime - Ubuntu 22.04 with only runtime dependencies
@@ -69,8 +71,12 @@ ENV DEBIAN_FRONTEND=noninteractive
6971
# Set the sandbox version as an environment variable for version checking
7072
ENV SANDBOX_VERSION=${SANDBOX_VERSION}
7173

72-
# Install essential runtime packages
73-
RUN apt-get update && apt-get install -y --no-install-recommends \
74+
# Install essential runtime packages with cache mounts
75+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
76+
--mount=type=cache,target=/var/lib/apt,sharing=locked \
77+
rm -f /etc/apt/apt.conf.d/docker-clean && \
78+
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache && \
79+
apt-get update && apt-get install -y --no-install-recommends \
7480
curl \
7581
wget \
7682
ca-certificates \
@@ -82,8 +88,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
8288
unzip \
8389
zip \
8490
jq \
85-
file \
86-
&& rm -rf /var/lib/apt/lists/*
91+
file
8792

8893
# Set Python 3.11 as default python3
8994
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
@@ -96,8 +101,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
96101
# Install Bun runtime from official image
97102
COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
98103

99-
# Install essential Python packages for code execution
100-
RUN pip3 install --no-cache-dir \
104+
# Install essential Python packages with cache mount
105+
RUN --mount=type=cache,target=/root/.cache/pip \
106+
pip3 install \
101107
matplotlib \
102108
numpy \
103109
pandas \

0 commit comments

Comments
 (0)