Hello Cloudflare Container (Rust)

Here's an example for Rust on Cloudflare via Container.

TLDR;

Setup

💡 Refer to https://developers.cloudflare.com/containers/

npm create cloudflare@latest -- --template=cloudflare/templates/containers-template

You will get Golang from template then just replace with Rust related below.

Config

# syntax=docker/dockerfile:1

#
# ---- Builder Stage ----
#
# ARG TARGETPLATFORM is an automatic variable provided by Docker buildx.
# It will be 'linux/amd64' or 'linux/arm64' depending on the --platform flag.
# We are setting a default value for builds that don't use the --platform flag.
ARG TARGETPLATFORM=linux/amd64

# Use a builder image with Rust installed.
FROM rust:1-alpine AS build

# Set build-time arguments that will be used in subsequent commands.
# This makes the value available inside the build stage.
ARG TARGETPLATFORM

# Install Zig, which will act as a universal C cross-compiler,
# then install cargo-zigbuild to integrate it with Cargo.
RUN apk add --no-cache zig build-base && \
    cargo install cargo-zigbuild

# Add the cargo bin directory to the PATH to make cargo-zigbuild available.
ENV PATH="/root/.cargo/bin:${PATH}"

# Set the working directory.
WORKDIR /app

# Copy the Cargo files first to leverage Docker's layer caching.
COPY container/Cargo.toml container/Cargo.lock ./

# This single RUN command determines the correct Rust target, installs it,
# and builds the project's dependencies using the robust cargo-zigbuild.
RUN case ${TARGETPLATFORM} in \
    "linux/amd64") RUST_TARGET="x86_64-unknown-linux-musl" ;; \
    "linux/arm64") RUST_TARGET="aarch64-unknown-linux-musl" ;; \
    esac && \
    rustup target add ${RUST_TARGET} && \
    mkdir -p src && \
    echo "fn main() {}" > src/main.rs && \
    cargo zigbuild --release --target ${RUST_TARGET}

# Now, copy the actual source code. This will be a separate layer.
COPY container/src/ ./src/

# Build the final application and create a stable symlink to the binary.
RUN case ${TARGETPLATFORM} in \
    "linux/amd64") RUST_TARGET="x86_64-unknown-linux-musl" ;; \
    "linux/arm64") RUST_TARGET="aarch64-unknown-linux-musl" ;; \
    esac && \
    rm -f target/${RUST_TARGET}/release/deps/server* && \
    cargo zigbuild --release --target ${RUST_TARGET} && \
    ln -s /app/target/${RUST_TARGET}/release/server /app/server

#
# ---- Final Stage ----
#
# Use a minimal 'scratch' image for the final container.
FROM scratch

# Copy the built binary using the stable symlink created in the build stage.
COPY --from=build /app/server /server

# Expose the port the application will listen on.
EXPOSE 8080

# Set the entrypoint for the container.
CMD ["/server"]
{{#include ../../../examples/r2/hello-cloudflare-container/container_src/Cargo.toml}}

Code

#![allow(unused)]
fn main() {
{{#include ../../../examples/r2/hello-cloudflare-container/container_src/src/main.rs}}
}
import { Container, loadBalance, getContainer } from "@cloudflare/containers";
import { Hono } from "hono";

export class MyContainer extends Container {
  // Port the container listens on (default: 8080)
  defaultPort = 8080;
  // Time before container sleeps due to inactivity (default: 30s)
  sleepAfter = "2m";
  // Environment variables passed to the container
  envVars = {
    MESSAGE: "I was passed in via the container class!",
  };

  // Optional lifecycle hooks
  override onStart() {
    console.log("Container successfully started");
  }

  override onStop() {
    console.log("Container successfully shut down");
  }

  override onError(error: unknown) {
    console.log("Container error:", error);
  }
}

// Create Hono app with proper typing for Cloudflare Workers
const app = new Hono<{
  Bindings: { MY_CONTAINER: DurableObjectNamespace<MyContainer> };
}>();

// Home route with available endpoints
app.get("/", (c) => {
  return c.text(
    "Available endpoints:\n" +
      "GET /container/<ID> - Start a container for each ID with a 2m timeout\n" +
      "GET /lb - Load balance requests over multiple containers\n" +
      "GET /error - Start a container that errors (demonstrates error handling)\n" +
      "GET /singleton - Get a single specific container instance",
  );
});

// Route requests to a specific container using the container ID
app.get("/hello", async (c) => {
  console.log("hello");
  const containerId = c.env.MY_CONTAINER.idFromName(`/container/1`);
  console.log("containerId:", containerId);
  const container = c.env.MY_CONTAINER.get(containerId);
  console.log("container:", container);
  return await container.fetch(c.req.raw);
});

// Route requests to a specific container using the container ID
app.get("/container/:id", async (c) => {
  const id = c.req.param("id");
  const containerId = c.env.MY_CONTAINER.idFromName(`/container/${id}`);
  const container = c.env.MY_CONTAINER.get(containerId);
  return await container.fetch(c.req.raw);
});

// Demonstrate error handling - this route forces a panic in the container
app.get("/error", async (c) => {
  const container = getContainer(c.env.MY_CONTAINER, "error-test");
  return await container.fetch(c.req.raw);
});

// Load balance requests across multiple containers
app.get("/lb", async (c) => {
  const container = await loadBalance(c.env.MY_CONTAINER, 3);
  return await container.fetch(c.req.raw);
});

// Get a single container instance (singleton pattern)
app.get("/singleton", async (c) => {
  const container = getContainer(c.env.MY_CONTAINER);
  return await container.fetch(c.req.raw);
});

export default app;

Dev & Deploy

npm run dev
npm run deploy