Hello Cloudflare Container (Rust)

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

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_src/Cargo.toml container_src/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/ ./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"]
[package]
name = "server"
version = "0.1.0"
authors = ["katopz"]
edition = "2021"
description = "A simple Rust (Actix) application on Cloudflare Container"
repository = "https://github.com/katopz/hello-cloudflare-container"
license = "MIT"
readme = "README.md"

[dependencies]
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }

[profile.release]
opt-level = "z"   # Optimize for size.
strip = true      # Automatically strip symbols from the binary.
lto = true        # Enable Link Time Optimization (LTO)
codegen-units = 1 # Reduce Parallel Code Generation Units to Increase Optimization
panic = "abort"

[target.x86_64-unknown-linux-musl]
linker = "gcc"

Code

use actix_web::{web, App, HttpServer, Responder};
use std::env;

async fn handler() -> impl Responder {
    let message = env::var("MESSAGE")
        .unwrap_or_else(|_| "🦀 I was passed in via the container class!".to_string());
    let instance_id = env::var("CLOUDFLARE_DEPLOYMENT_ID").unwrap_or_else(|_| "local".to_string());
    format!(
        "🦀 Hi, I'm a container and this is my message: \"{}\", my instance ID is: {}",
        message, instance_id
    )
}

async fn error_handler() -> impl Responder {
    // This handler is intended to panic, but we must return a type that
    // implements Responder. We will never actually return this response.
    panic!("This is a panic");
    #[allow(unreachable_code)]
    Ok::<_, actix_web::Error>(())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/error", web::get().to(error_handler))
            .default_service(web::to(handler))
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}
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