Hello Cloudflare Container (Rust)
Here's an example for
Rust
on Cloudflare
via Container
.
Setup
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