Cloudflare Containers

repository·main·Indexed 42 days ago

https://github.com/cloudflare/containers

A library for interacting with and managing containerized workloads directly from Cloudflare Workers. It provides a helper class for container-enabled Durable Objects, enabling developers to proxy HTTP/WebSocket traffic, manage container lifecycles, control outbound egress with host allow/deny lists, and implement R2-backed FUSE mounts for object storage.

Tokens
15.8K
Snippets
49
Records
63
Agent score
55%

What's inside Cloudflare Containers

  1. Features of Cloudflare Containers

    main

    The @cloudflare/containers package provides the following capabilities for interacting with containers on Cloudflare Workers:

    • Request Handling: HTTP request proxying and WebSocket forwarding.
    • Egress Control: Outbound request interception by host or catch-all handler, including HTTPS support.
    • Security: Host allow/deny lists using glob pattern matching.
    • Lifecycle Management: Simple starting and stopping of containers.
    • Lifecycle Hooks: Event hooks for onStart, onStop, and onError.
    • Resource Management: Configurable sleep timeout that renews upon receiving requests.
    • Scaling: Load balancing utilities.
  2. Mounting object storage as FUSE mounts

    main

    Mounting R2 buckets as FUSE mounts allows applications to interact with object storage as if it were a local filesystem. This is particularly useful for applications that lack native object storage support or to simplify file operations.

    Key Considerations:

    • Compatibility: Object storage is not fully POSIX-compatible and is not local storage.
    • Performance: Do not expect native SSD-like performance. It is best suited for use cases like reading shared assets, bootstrapping agents/sandboxes, or persisting user state where I/O intensity is low.
  3. Configure outbound request interception

    main

    Outbound interception allows you to control container egress or proxy requests through Worker code.

    Processing Order (First Match Wins)

    1. Denied hosts: Matches in deniedHosts are blocked (HTTP 520).
    2. Allowed hosts gate: If allowedHosts is non-empty, non-matching hosts are blocked (HTTP 520).
    3. Runtime per-host handler: Set via setOutboundByHost().
    4. Static per-host handler: Defined in outboundByHost.
    5. Runtime catch-all handler: Set via setOutboundHandler().
    6. Static catch-all handler: Defined in outbound.
    7. Allowed host internet fallback: If host matched allowedHosts but no handler matched, it goes to the internet.
    8. enableInternet fallback: If true, request goes to the internet.
    9. Default deny: Request is blocked (HTTP 520).

    Runtime Management

    • setOutboundByHost(host, method): Routes a specific hostname to a named handler from static outboundHandlers.
    • setOutboundByHosts(handlers): Replaces all runtime host-specific overrides.
    • setOutboundHandler(method): Sets the catch-all outbound handler.
    • setAllowedHosts(hosts) / setDeniedHosts(hosts): Replaces the lists at runtime.
    • allowHost(hostname) / denyHost(hostname): Modifies lists individually.

    Static Configuration

    • static outbound: Catch-all handler for outbound requests.
    • static outboundByHost: Map of { [host]: handler } for exact matches.
    • static outboundHandlers: Map of { [name]: handler } for selecting handlers at runtime.
  4. The `Container` Class

    main

    The Container class extends a container-enabled Durable Object to provide specialized functionality for managing container lifecycles, networking, and outbound traffic. It allows you to start, stop, and communicate with containerized instances directly from your Durable Object code.

    import { Container } from '@cloudflare/containers';
    
    export class MyContainer extends Container {
      // Override lifecycle hooks or use container methods
    }
  5. Use WebSocket support in Containers

    main

    The Container class automatically proxies WebSocket connections bi-directionally. Messages are forwarded in both directions, and the container automatically renews its activity timeout whenever WebSocket messages are sent or received.

    Note: WebSockets are not supported with containerFetch. To connect to a WebSocket on a specific port, use switchPort with container.fetch.

    // Connect to a WebSocket on port 9000
    const response = await container.fetch(switchPort(request, 9000));
  6. Understand the outbound request processing order

    main

    When a container makes an outbound request, it is evaluated against these rules in order. The first match wins:

    1. Denied hosts: If hostname matches deniedHosts, block (HTTP 520). Highest priority.
    2. Allowed hosts gate: If allowedHosts is non-empty and hostname does not match, block (HTTP 520).
    3. Per-host handler (runtime): Invoked if setOutboundByHost() was used.
    4. Per-host handler (static): Invoked if outboundByHost contains the hostname.
    5. Catch-all handler (runtime): Invoked if setOutboundHandler() was used.
    6. Catch-all handler (static): Invoked if outbound is defined.
    7. Allowed host internet fallback: If hostname matched allowedHosts but no handler matched, forward to public internet.
    8. enableInternet fallback: If enableInternet is true, forward to public internet.
    9. Default deny: Block (HTTP 520).
  7. Configure HTTPS interception and trust the Cloudflare CA

    main

    When interceptHttps is set to true, outbound HTTPS traffic is intercepted. To prevent SSL errors, your container must trust the Cloudflare-provided CA certificate located at /etc/cloudflare/certs/cloudflare-containers-ca.crt.

    Because this certificate is ephemeral and only available at runtime, you must copy it into your distribution's certificate directory and refresh the trust store in your container's entrypoint before the application starts.

    Distribution-specific setup

    DistributionCertificate directoryUpdate command
    Alpine/usr/local/share/ca-certificates/update-ca-certificates
    Debian/Ubuntu/usr/local/share/ca-certificates/update-ca-certificates
    Fedora/RHEL/etc/pki/ca-trust/source/anchors/update-ca-trust
    Arch/etc/ca-certificates/trust-source/anchors/trust extract-compat

    Example: Alpine/Debian/Ubuntu entrypoint

    export class MyContainer extends Container {
      interceptHttps = true;
      entrypoint = [
        'sh',
        '-lc',
        'cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /usr/local/share/ca-certificates/cloudflare-containers-ca.crt && update-ca-certificates && exec node server.js',
      ];
    }

    Manual CA configuration for specific runtimes

    If your runtime does not use the system trust store, point it to the certificate directly:

    • Node.js: export NODE_EXTRA_CA_CERTS=/etc/cloudflare/certs/cloudflare-containers-ca.crt
    • Python (requests): export REQUESTS_CA_BUNDLE=/etc/cloudflare/certs/cloudflare-containers-ca.crt
    • curl: curl --cacert /etc/cloudflare/certs/cloudflare-containers-ca.crt https://example.com
  8. Route requests to specific or load-balanced containers

    main

    You can route incoming Worker requests to containers using two primary patterns:

    1. Named Containers: Use .getByName(identifier) to route requests to a specific container instance associated with a unique ID (e.g., a specific pathname).
    2. Load Balancing: Use the getRandom(binding, count) helper to distribute incoming requests across a pool of container instances. This is useful for basic load balancing until built-in autoscaling is available.

    Note: env.MY_CONTAINER refers to the container binding defined in your wrangler.toml or Workers environment.

    import { Container, getContainer, getRandom } from '@cloudflare/containers';
    
    export class MyContainer extends Container {
      defaultPort = 8080;
      sleepAfter = '1m';
    }
    
    export default {
      async fetch(request, env) {
        const pathname = new URL(request.url).pathname;
    
        // Route to a specific container based on pathname
        if (pathname.startsWith('/specific/')) {
          const container = env.MY_CONTAINER.getByName(pathname);
          return await container.fetch(request);
        }
    
        // Load balance across 5 container instances
        let container = await getRandom(env.MY_CONTAINER, 5);
        return await container.fetch(request);
      },
    };
  9. Configure Egress (Outbound Interception) for Containers

    main

    You can control and intercept outbound network requests from a container by configuring properties on your Container subclass.

    Core Configuration Properties

    • enableInternet: Set to true to allow outbound internet access. If false, all outbound requests are blocked unless explicitly handled.
    • interceptHttps: When true, HTTPS requests are intercepted to allow inspection or modification.
    • allowedHosts: An array of hostnames (e.g., ['google.com']) that are permitted to make outbound requests.
    • deniedHosts: An array of hostnames that are explicitly blocked. If a host is in both allowedHosts and deniedHosts, it is blocked.

    Outbound Handlers

    You can define how specific outbound requests are handled using three levels of priority:

    1. outboundByHost: A mapping of hostnames to handler functions. This is the highest priority.
    2. outboundHandlers: A mapping of named handlers. You can trigger these from your application code using container.setOutboundByHost(host, handlerName, params).
    3. outbound: A catch-all function that handles any outbound request not matched by the previous two methods.
    import { Container, getContainer } from '@cloudflare/containers';
    
    export class MyContainer extends Container {
      defaultPort = 8080;
      enableInternet = false;
      interceptHttps = true;
    
      allowedHosts = ['api.stripe.com', 'google.com', 'github.com'];
      deniedHosts = ['evil.com'];
    }
    
    // 1. Highest priority: Specific host mapping
    MyContainer.outboundByHost = {
      'google.com': (req, env, ctx) => {
        return new Response('intercepted google for ' + ctx.containerId);
      },
    };
    
    // 2. Named handlers (invoked via setOutboundByHost)
    MyContainer.outboundHandlers = {
      async github(req, env, ctx) {
        return new Response('github handler, ' + ctx.params?.hello);
      },
    };
    
    // 3. Lowest priority: Catch-all
    MyContainer.outbound = req => {
      return new Response(`catch-all for ${new URL(req.url).hostname}`);
    };
    
    export default {
      async fetch(request, env) {
        const container = getContainer(env.MY_CONTAINER);
        // Trigger a named handler for a specific host with custom parameters
        await container.setOutboundByHost('github.com', 'github', { hello: 'world' });
        return await container.fetch(request);
      },
    };