Repix Logo
Deployment

Cloudflare Workers with Sandbox

Run Repix on Cloudflare Workers using the Sandbox SDK for isolated container execution.

Cloudflare Sandbox SDK lets you run containers from Workers. Since Repix uses Sharp (native Node.js + libvips), it requires a container environment. The Sandbox SDK provides this.

Workers Paid plan required. The Sandbox SDK is available on Cloudflare's Workers Paid plan. A custom domain with wildcard DNS is required for preview URLs in production (e.g., *.images.yourdomain.com).

Architecture

  1. A Worker receives image transformation requests
  2. The Worker gets a Sandbox container running Repix
  3. Repix runs inside the Sandbox with full Sharp/libvips support
  4. The Worker proxies requests to the Sandbox and returns the response

Setup

1. Install Dependencies

npm install @cloudflare/sandbox wrangler

2. Sandbox Dockerfile for Repix

Create a Dockerfile that packages Repix for the Sandbox. The Sandbox base includes Node.js; we add Sharp dependencies:

# Dockerfile.sandbox
FROM node:22-alpine

# Sharp dependencies
RUN apk add --no-cache vips-dev build-base python3 make g++ pkgconfig

WORKDIR /app

# Copy repix
COPY package*.json ./
COPY tsconfig.json ./
COPY vite.config.ts ./

RUN npm ci --include=dev
COPY src/ ./src/
RUN npm run build && npm ci --only=production --omit=dev

EXPOSE 3210
CMD ["node", "dist/index.js"]
Alternatively, use the existing Repix Dockerfile if it's compatible with Sandbox. The key is that the image must run Repix on port 3210 and expose it.

3. Wrangler Configuration

Create wrangler.jsonc in a new worker directory (or at repo root if this is a Worker-only project):

{
  "name": "repix-worker",
  "main": "src/index.ts",
  "compatibility_date": "2024-01-01",
  "containers": [
    {
      "class_name": "Sandbox",
      "image": "../Dockerfile.sandbox",
      "instance_type": "lite",
      "max_instances": 5,
    },
  ],
  "durable_objects": {
    "bindings": [{ "class_name": "Sandbox", "name": "Sandbox" }],
  },
  "migrations": [{ "new_sqlite_classes": ["Sandbox"], "tag": "v1" }],
}

4. Worker Code

// src/index.ts
import { getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
  async fetch(request: Request, env: { Sandbox: DurableObjectNamespace }) {
    const url = new URL(request.url);

    // Forward API routes (/, /health, /presets) and image requests (/:transform/*)
    const pathSegments = url.pathname.split("/").filter(Boolean);
    const isApiRoute =
      url.pathname === "/" ||
      url.pathname === "/health" ||
      url.pathname === "/presets";
    const isImageRequest = pathSegments.length >= 2;
    if (!isApiRoute && !isImageRequest) {
      return new Response(
        JSON.stringify({
          service: "Repix",
          endpoint: "/{preset|params}/{source-url}",
        }),
        { headers: { "Content-Type": "application/json" } },
      );
    }

    const sandbox = getSandbox(env.Sandbox, "repix");

    // Expose Repix's port - returns URL to the running service
    const { url: sandboxUrl } = await sandbox.exposePort(3210);

    // Forward request to Repix running in the Sandbox
    const repixUrl = `${sandboxUrl}${url.pathname}${url.search}`;
    const response = await fetch(repixUrl, {
      method: request.method,
      headers: request.headers,
      body: request.body,
    });

    return new Response(response.body, {
      status: response.status,
      headers: response.headers,
    });
  },
};

5. Deploy

cd worker
npx wrangler deploy

Important Notes

Cold Starts

Sandbox containers start on first use. The container sleeps after ~10 minutes of inactivity. Expect 5–15 second cold starts for the first request after sleep.

Preview URLs

Production preview URLs require a custom domain with wildcard DNS:

  • Add domain: images.yourdomain.com with *.images.yourdomain.com
  • The Sandbox preview URL will use this for serving

Cost

Sandbox usage is billed separately. Monitor usage in the Cloudflare dashboard.

References

Copyright © 2026