Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/violet-needles-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/sandbox": patch
---

Debloat base docker image (2.63GB → 1.03GB)
118 changes: 93 additions & 25 deletions .github/changeset-version.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { execSync } from "node:child_process";
import * as fs from "node:fs";
import fg from "fast-glob";

// This script is used by the `release.yml` workflow to update the version of the packages being released.
// The standard step is only to run `changeset version` but this does not update the package-lock.json file.
Expand All @@ -13,41 +14,108 @@ execSync("npm install", {
stdio: "inherit",
});

// Update Dockerfile and README version references after changeset updates package.json
// Update all version references across the codebase after changeset updates package.json
try {
const packageJson = JSON.parse(fs.readFileSync("./packages/sandbox/package.json", "utf-8"));
const newVersion = packageJson.version;

const dockerfilePath = "./examples/basic/Dockerfile";
let dockerfileContent = fs.readFileSync(dockerfilePath, "utf-8");
console.log(`\n🔍 Searching for version references to update to ${newVersion}...\n`);

// Update the production image version in the comment
dockerfileContent = dockerfileContent.replace(
/# FROM docker\.io\/cloudflare\/sandbox:[\d.]+/,
`# FROM docker.io/cloudflare/sandbox:${newVersion}`
);
// Patterns to match version references in different contexts
const versionPatterns = [
// Docker image versions (production and test)
{
pattern: /FROM docker\.io\/cloudflare\/sandbox:[\d.]+/g,
replacement: `FROM docker.io/cloudflare/sandbox:${newVersion}`,
description: "Production Docker image",
},
{
pattern: /# FROM docker\.io\/cloudflare\/sandbox:[\d.]+/g,
replacement: `# FROM docker.io/cloudflare/sandbox:${newVersion}`,
description: "Commented production Docker image",
},
{
pattern: /FROM cloudflare\/sandbox-test:[\d.]+/g,
replacement: `FROM cloudflare/sandbox-test:${newVersion}`,
description: "Test Docker image",
},
{
pattern: /docker\.io\/cloudflare\/sandbox-test:[\d.]+/g,
replacement: `docker.io/cloudflare/sandbox-test:${newVersion}`,
description: "Test Docker image (docker.io)",
},
// Image tags in docker commands
{
pattern: /cloudflare\/sandbox:[\d.]+/g,
replacement: `cloudflare/sandbox:${newVersion}`,
description: "Docker image reference",
},
{
pattern: /cloudflare\/sandbox-test:[\d.]+/g,
replacement: `cloudflare/sandbox-test:${newVersion}`,
description: "Test Docker image reference",
},
];

// Update the test image version
dockerfileContent = dockerfileContent.replace(
/FROM cloudflare\/sandbox-test:[\d.]+/,
`FROM cloudflare/sandbox-test:${newVersion}`
);
// Files to search and update
const filePatterns = [
"**/*.md", // All markdown files
"**/Dockerfile", // All Dockerfiles
"**/Dockerfile.*", // Dockerfile variants
"**/*.ts", // TypeScript files (for documentation comments)
"**/*.js", // JavaScript files
"**/*.json", // JSON configs (but not package.json/package-lock.json)
"**/*.yaml", // YAML configs
"**/*.yml", // YML configs
];

fs.writeFileSync(dockerfilePath, dockerfileContent);
console.log(`✅ Updated Dockerfile versions to ${newVersion}`);
// Ignore patterns
const ignorePatterns = [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/.git/**",
"**/package.json", // Don't modify package.json (changeset does this)
"**/package-lock.json", // Don't modify package-lock.json (npm install does this)
"**/.github/changeset-version.ts", // Don't modify this script itself
];

// Update README.md
const readmePath = "./README.md";
let readmeContent = fs.readFileSync(readmePath, "utf-8");
// Find all matching files
const files = await fg(filePatterns, {
ignore: ignorePatterns,
onlyFiles: true,
});

// Update the Docker image version in README
readmeContent = readmeContent.replace(
/FROM docker\.io\/cloudflare\/sandbox:[\d.]+/,
`FROM docker.io/cloudflare/sandbox:${newVersion}`
);
console.log(`📁 Found ${files.length} files to check\n`);

fs.writeFileSync(readmePath, readmeContent);
console.log(`✅ Updated README.md version to ${newVersion}`);
let updatedFilesCount = 0;
let totalReplacementsCount = 0;

for (const file of files) {
let content = fs.readFileSync(file, "utf-8");
let fileModified = false;
let fileReplacementsCount = 0;

// Try all patterns on this file
for (const { pattern, replacement, description } of versionPatterns) {
const matches = content.match(pattern);
if (matches) {
content = content.replace(pattern, replacement);
fileModified = true;
fileReplacementsCount += matches.length;
}
}

if (fileModified) {
fs.writeFileSync(file, content);
updatedFilesCount++;
totalReplacementsCount += fileReplacementsCount;
console.log(` ✅ ${file} (${fileReplacementsCount} replacement${fileReplacementsCount > 1 ? 's' : ''})`);
}
}

console.log(`\n✨ Updated ${totalReplacementsCount} version reference${totalReplacementsCount !== 1 ? 's' : ''} across ${updatedFilesCount} file${updatedFilesCount !== 1 ? 's' : ''}`);
console.log(` New version: ${newVersion}\n`);

} catch (error) {
console.error("❌ Failed to update file versions:", error);
Expand Down
144 changes: 70 additions & 74 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,79 +64,77 @@
run: npm run test -w @repo/sandbox-container

# E2E tests run in parallel with unit tests
# e2e-tests:
# if: ${{ github.repository_owner == 'cloudflare' }}
# runs-on: ubuntu-latest
# timeout-minutes: 30

# steps:
# - uses: actions/checkout@v4

# - uses: actions/setup-node@v4
# with:
# node-version: 24
# cache: "npm"

# - uses: oven-sh/setup-bun@v2
# with:
# bun-version: latest

# - name: Install dependencies
# run: npm ci

# - name: Build packages
# run: npm run build

# - name: Set worker name
# id: worker-name
# run: |
# # Use git SHA for unique, meaningful naming (not sequential run number)
# SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
# echo "worker_name=sandbox-e2e-test-worker-release-${SHORT_SHA}" >> $GITHUB_OUTPUT

# # Generate unique wrangler config for this release
# - name: Generate wrangler config
# run: |
# cd tests/e2e/test-worker
# ./generate-config.sh ${{ steps.worker-name.outputs.worker_name }}

# - name: Build test worker Docker image
# run: npm run docker:local -w @cloudflare/sandbox

# - name: Deploy test worker
# uses: cloudflare/wrangler-action@v3
# with:
# apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
# accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# command: deploy --name ${{ steps.worker-name.outputs.worker_name }}
# workingDirectory: tests/e2e/test-worker

# - name: Get deployment URL
# id: get-url
# run: |
# echo "worker_url=https://${{ steps.worker-name.outputs.worker_name }}.agents-b8a.workers.dev" >> $GITHUB_OUTPUT

# - name: Run E2E tests
# run: npx vitest run --config vitest.e2e.config.ts
# env:
# TEST_WORKER_URL: ${{ steps.get-url.outputs.worker_url }}
# CI: true

# - name: Cleanup test deployment
# if: always()
# continue-on-error: true
# run: |
# cd tests/e2e/test-worker
# ../../../scripts/cleanup-test-deployment.sh ${{ steps.worker-name.outputs.worker_name }}
# env:
# CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
# CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
e2e-tests:
if: ${{ github.repository_owner == 'cloudflare' }}
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 24
cache: "npm"

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: npm ci

- name: Build packages
run: npm run build

- name: Set worker name
id: worker-name
run: |
# Use git SHA for unique, meaningful naming (not sequential run number)
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
echo "worker_name=sandbox-e2e-test-worker-release-${SHORT_SHA}" >> $GITHUB_OUTPUT

# Generate unique wrangler config for this release
- name: Generate wrangler config
run: |
cd tests/e2e/test-worker
./generate-config.sh ${{ steps.worker-name.outputs.worker_name }}

- name: Build test worker Docker image
run: npm run docker:local -w @cloudflare/sandbox

- name: Deploy test worker
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --name ${{ steps.worker-name.outputs.worker_name }}
workingDirectory: tests/e2e/test-worker

- name: Get deployment URL
id: get-url
run: |
echo "worker_url=https://${{ steps.worker-name.outputs.worker_name }}.agents-b8a.workers.dev" >> $GITHUB_OUTPUT

- name: Run E2E tests
run: npx vitest run --config vitest.e2e.config.ts
env:
TEST_WORKER_URL: ${{ steps.get-url.outputs.worker_url }}
CI: true

- name: Cleanup test deployment
if: always()
continue-on-error: true
run: |
cd tests/e2e/test-worker
../../../scripts/cleanup-test-deployment.sh ${{ steps.worker-name.outputs.worker_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

# Prerelease publish - always runs after tests pass
publish-prerelease:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
needs: [unit-tests,
# e2e-tests
]
needs: [unit-tests, e2e-tests]
if: ${{ github.repository_owner == 'cloudflare' }}
runs-on: ubuntu-latest
timeout-minutes: 20
Expand Down Expand Up @@ -187,9 +185,7 @@

# Release publish - only runs if changesets exist
publish-release:
needs: [unit-tests,
# e2e-tests
]
needs: [unit-tests, e2e-tests]
if: ${{ github.repository_owner == 'cloudflare' }}
runs-on: ubuntu-latest
timeout-minutes: 20
Expand Down Expand Up @@ -236,4 +232,4 @@
env:
GITHUB_TOKEN: ${{ secrets.SANDBOX_GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
2 changes: 1 addition & 1 deletion examples/basic/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ FROM cloudflare/sandbox-test:0.4.2

# On a mac, you might need to actively pick up the
# arm64 build of the image.
# FROM --platform=linux/arm64 cloudflare/sandbox-test:0.1.3
# FROM --platform=linux/arm64 cloudflare/sandbox-test:0.4.2

# Expose the ports you want to expose
EXPOSE 8080
Expand Down
6 changes: 3 additions & 3 deletions examples/code-interpreter/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# This image is unique to this repo, and you'll never need it.
# Whenever you're integrating with sandbox SDK in your own project,
# you should use the official image instead:
# FROM docker.io/cloudflare/sandbox:0.2.3
FROM cloudflare/sandbox-test:0.2.3
# FROM docker.io/cloudflare/sandbox:0.4.2
FROM cloudflare/sandbox-test:0.4.2

# On a mac, you might need to actively pick up the
# arm64 build of the image.
# FROM --platform=linux/arm64 cloudflare/sandbox-test:0.1.3
# FROM --platform=linux/arm64 cloudflare/sandbox-test:0.4.2
4 changes: 2 additions & 2 deletions examples/minimal/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.io/cloudflare/sandbox:0.3.6
FROM docker.io/cloudflare/sandbox:0.4.2

# On a Mac with Apple Silicon, you might need to specify the platform:
# FROM --platform=linux/arm64 docker.io/cloudflare/sandbox:0.3.6
# FROM --platform=linux/arm64 docker.io/cloudflare/sandbox:0.4.2
Loading