Deploy guides
Axum API on EC2 Rust EC2
Deploy an Axum (or any Rust binary) API to a managed EC2 server. Cumulus cross-compiles the binary on an ephemeral Fargate task in your AWS account, uploads it, and swaps the running service with zero downtime. No build toolchain on your machine, no CI to maintain.
1. Provision a server (once)
Run this once to create the server. Cumulus handles everything: the instance, networking, and the agent that receives deploys.
cumulus server create prod-1 \
--instance-type t4g.small # Graviton, ~$13/mo
• waiting for running…
✓ server prod-1 is ready → 54.12.34.56
2. Configure cumulus.toml
[project]
name = "my-saas"
region = "us-east-1"
[[app]]
name = "api"
runtime = "rust"
target = "ec2"
binary = "api" # the [[bin]] name in Cargo.toml
server = "prod-1" # the server you just created
[app.env]
DATABASE_URL = { secret = "/cumulus/api/DATABASE_URL" }
LOG_LEVEL = "info"
3. Set secrets and deploy
cumulus app env-set api DATABASE_URL="postgres://user:pass@host/db" --secret
cumulus app deploy api
• server `prod-1` → 54.12.34.56 (i-0abc123def456)
• building `api`…
• built
• artifact uploaded
• agent deployed
• process settled (no health check)
• cut over
✓ deployed `api` to `prod-1`
[app.health_check] to probe a live readiness endpoint instead of waiting a fixed interval. If the probe returns non-2xx, Cumulus automatically rolls back to the previous release.
[app.health_check]
path = "/health"
port = 8080
timeout_s = 10
Django on EC2 Python EC2
Django apps run under uvicorn (ASGI) or gunicorn (WSGI), managed by systemd. Dependencies are installed on-server with uv, so no Python version needs to be installed on the host.
cumulus.toml
[project]
name = "my-saas"
region = "us-east-1"
[[app]]
name = "web"
runtime = "python"
target = "ec2"
server = "prod-1"
[app.python]
version = "3.12"
entry = "myproject.asgi:application" # Django ASGI entrypoint
server = "uvicorn"
workers = 2
[app.env]
DJANGO_SETTINGS_MODULE = "myproject.settings.production"
SECRET_KEY = { secret = "/cumulus/web/SECRET_KEY" }
DATABASE_URL = { secret = "/cumulus/web/DATABASE_URL" }
[app.tasks]
migrate = "python manage.py migrate --noinput"
collectstatic = "python manage.py collectstatic --noinput"
# Run migrations before the new release goes live
release = ["migrate", "collectstatic"]
[[app]]
name = "web"
runtime = "python"
target = "ec2"
server = "prod-1"
[app.python]
version = "3.12"
entry = "myproject.wsgi:application"
server = "gunicorn"
workers = 4
Dependency management
Cumulus detects your dependency manager automatically:
Deploy
cumulus app env-set web SECRET_KEY="your-secret" --secret
cumulus app env-set web DATABASE_URL="postgres://..." --secret
cumulus app deploy web
FastAPI on EC2 Python EC2
cumulus.toml
[project]
name = "my-api"
region = "us-east-1"
[[app]]
name = "api"
runtime = "python"
target = "ec2"
server = "prod-1"
[app.python]
version = "3.12"
entry = "main:app" # `app = FastAPI()` in main.py
server = "uvicorn"
workers = 2
[app.env]
DATABASE_URL = { secret = "/cumulus/api/DATABASE_URL" }
ENV = "production"
Minimal FastAPI app
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello World"}
# requirements.txt (or pyproject.toml, or uv.lock)
fastapi
uvicorn[standard]
cumulus app deploy api
FastAPI on Lambda Python Lambda
Run FastAPI on Lambda via Mangum, the ASGI adapter for AWS Lambda. Cumulus packages your source and dependencies in a Lambda-compatible container and deploys them to a managed Python runtime.
cumulus.toml
[project]
name = "my-api"
region = "us-east-1"
[[app]]
name = "api"
runtime = "python"
target = "lambda"
[app.lambda]
handler = "main.handler" # module.function
python_version = "3.12"
memory_mb = 512
timeout_s = 29
architecture = "arm64"
[app.lambda.url]
enabled = true # creates a public Function URL
auth = "none"
[app.env]
DATABASE_URL = { secret = "/cumulus/api/DATABASE_URL" }
main.py
# main.py
from fastapi import FastAPI
from mangum import Mangum
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
handler = Mangum(app, lifespan="off")
# requirements.txt
fastapi
mangum
cumulus app deploy api
• packaging Python 3.12…
• deploying function `api`…
✓ deployed `api` (arn:aws:lambda:us-east-1:123456789012:function:api)
version: 3
function URL: https://abc123.lambda-url.us-east-1.on.aws/
Python handler on Lambda Python Lambda
A bare def handler(event, context) for event-driven processing, cron jobs, SQS consumers, and S3 triggers.
cumulus.toml
[project]
name = "workers"
region = "us-east-1"
[[app]]
name = "processor"
runtime = "python"
target = "lambda"
[app.lambda]
handler = "handler.process" # handler.py → def process(event, ctx)
python_version = "3.12"
memory_mb = 256
timeout_s = 60
architecture = "arm64"
[app.env]
QUEUE_URL = { secret = "/cumulus/processor/QUEUE_URL" }
handler.py
# handler.py
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def process(event, context):
logger.info("Received %d records", len(event.get("Records", [])))
for record in event.get("Records", []):
body = json.loads(record["body"])
# do the work…
return {"statusCode": 200, "body": "ok"}
cumulus app deploy processor
[app.lambda.url] needed for event-driven handlers. Lambda invokes them directly from the trigger (SQS, S3, EventBridge, etc.).
Multiple handlers, one project
Each handler is a separate [[app]] entry. They can share source or be in separate directories:
[[app]]
name = "ingest"
runtime = "python"
target = "lambda"
source = "handlers/ingest"
[app.lambda]
handler = "main.handler"
memory_mb = 128
timeout_s = 30
[[app]]
name = "notify"
runtime = "python"
target = "lambda"
source = "handlers/notify"
[app.lambda]
handler = "main.handler"
memory_mb = 128
timeout_s = 10
cumulus app deploy # deploy all apps
cumulus app deploy ingest # deploy one app
Rust on Lambda Rust Lambda
Rust Lambda handlers use the aws-lambda-rust-runtime crate. Cumulus handles the build and packaging automatically, with no manual cross-compilation steps needed.
Cargo.toml
[[bin]]
name = "handler"
path = "src/main.rs"
[dependencies]
lambda_runtime = "0.13"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
src/main.rs
// src/main.rs
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Request { name: String }
#[derive(Serialize)]
struct Response { message: String }
async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
Ok(Response { message: format!("Hello, {}!", event.payload.name) })
}
#[tokio::main]
async fn main() -> Result<(), Error> {
run(service_fn(function_handler)).await
}
cumulus.toml
[project]
name = "my-service"
region = "us-east-1"
[[app]]
name = "handler"
runtime = "rust"
target = "lambda"
binary = "handler" # the [[bin]] name
[app.lambda]
memory_mb = 256
timeout_s = 29
architecture = "arm64" # Graviton: cheaper and faster for Rust
[app.lambda.url]
enabled = true
auth = "none"
cumulus app deploy handler
• building `handler`…
• deploying function `handler`…
✓ deployed `handler` (arn:aws:lambda:...)
version: 7
function URL: https://xyz.lambda-url.us-east-1.on.aws/
Static site on S3 Static S3
Next.js, Vite, Hugo, Gatsby, or any build that outputs a directory of files. Cumulus syncs files to S3 with the right cache headers (HTML no-cache, hashed assets immutable), and optionally provisions a CloudFront distribution with an ACM certificate.
[project]
name = "my-saas"
region = "us-east-1"
[[app]]
name = "frontend"
runtime = "static"
target = "s3"
source = "apps/frontend" # optional, omit for the repo root
[app.s3]
bucket = "my-saas-frontend"
kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/<uuid>"
[app.build]
command = "npm run build"
output_dir = "out" # Next.js static export output
node_version = "20"
[app.cdn]
enabled = true
custom_domain = "app.example.com"
output: 'export' in next.config.js.
[app.build]
command = "npm run build"
output_dir = "dist"
node_version = "20"
[app.build]
command = "hugo --minify"
output_dir = "public"
node_version field only applies to npm builds.cumulus app deploy frontend
✓ uploaded 47, deleted 3 → s3://my-saas-frontend
• provisioning custom domain app.example.com (ACM + CloudFront; first run takes a few minutes)…
✓ app.example.com → d3abc123.cloudfront.net (CloudFront E1ABC123)