---
title: "i cut cold-start to 10 ms with rust + serverless sqlite—here’s the terraform template"
author: "pilput"
canonical: "https://pilput.net/pilput/i-cut-cold-start-to-10-ms-with-rust-serverless-sqliteheres-the-terraform-template"
published: "2025-07-31T05:58:15.188328Z"
updated: "2025-07-31T05:58:15.188328Z"
description: "why another cold-start article? every millisecond you shave off an aws lambda cold-start matters for seo . google core web vitals punish you when the first 200..."
---
# i cut cold-start to 10 ms with rust + serverless sqlite—here’s the terraform template

## why another cold-start article?

  every millisecond you shave off an aws lambda cold-start matters for **seo**.
  google core web vitals punish you when the first 200 ms aren’t blazingly fast, and a cold-started lambda with a traditional postgres or mysql client can easily cost you **600 ms** before your code even starts doing business logic.
  in this post we’ll bring the cold-start down to **~10 ms** on x86-64 lambda using rust + precompiled serverless sqlite.
  all infrastructure will be reproducible with a short **terraform** template—perfect for a **full-stack** hobby project or a production mvp.

## tl;dr repo, then deep-dive

copy the github repo and `terraform apply`.
**repo:** `https://github.com/your-handle/10ms-coldstart-lambda-rust`

```
git clone https://github.com/your-handle/10ms-coldstart-lambda-rust.git
cd 10ms-coldstart-lambda-rust
terraform init && terraform apply -auto-approve
```

if you prefer reading first, keep scrolling.

## what you will learn

- how to cross-compile rust for lambda's **al2** environment on your laptop (windows, macos or linux)

- a minimalist sqlite setup with [sqlx](https://github.com/krysopath/sqlx-stateless) that stores the _entire_ database inside the same lambda package

- a cloudformation-free lambda zip layer created entirely by **devops** friendly **hcl**

- how to wire it up to api gateway http apis via terraform

- one-liner prometheus push to datadog for long-term observability

## architecture overview

![simple lambda with sqlite inside the same filesystem](https://i.imgur.com/7hwqwrd.png)

### key files & folders

```
.
├── cargo.toml         # rust workspace
├── src/
│   └── main.rs        # axum handler
├── assets/
│   └── app.db         # read-only sqlite db baked into lambda
├── terraform/
│   ├── main.tf        # single‐file infra
│   ├── variables.tf
│   └── outputs.tf
└── makefile           # easy aliases (make build, make deploy)
```

## step 1—write the micro-service in rust

### dependencies (cargo.toml)

```
[package]
name    = "coldstart_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
axum            = "0.7"
tokio           = { version = "1", features = ["macros", "rt-multi-thread"] }
serde           = { version = "1", features = ["derive"] }
sqlx            = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite"] }
tracing         = "0.1"
tracing-subscriber = "0.3"
tower-http      = { version = "0.5", features = ["trace"] }
lambda_http     = "0.9"
```

### the handler (src/main.rs)

```
use axum::{
    extract::{path, state},
    response::json,
    routing::get,
    router,
    serve,
};
use sqlx::{sqlitepool, sqlite::sqlitepooloptions};
use std::sync::arc;

type sharedpool = arc;

#[derive(serde::serialize)]
struct product {
    id: i64,
    name: string,
}

async fn product(pool: state, path(id): path_) -> json

 {
    let row = sqlx::query_as!(
        product,
        "select id, name from products where id = ?",
        id
    )
    .fetch_one(&*pool.0)
    .await
    .expect("missing sku");
    json(row)
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let pool = sqlitepooloptions::new()
        .connect("sqlite:./assets/app.db?mode=ro")
        .await
        .expect("open db");

    let app = router::new()
        .route("/products/:id", get(product))
        .with_state(arc::new(pool));

    serve(lambda_http::run(app), std::convert::identity).await;
}
```

#### a trivial sqlite schema

the production sample simply adds **5 k rows** into `assets/app.db`:

```
create table products(
  id   integer primary key,
  name text not null
);

insert into products(name)
select 'product ' || value
from generate_series(1, 5000);
```

## step 2—build a lightning-fast binary

the entire build is reproducible in a makefile:

```
# makefile
image ?= public.ecr.aws/lambda/provided:al2-x86_64
func  ?= coldstart-rust

build:
	docker run --rm \
	  -v $(pwd):/code \
	  -w /code \
	  rust:1.79 \
	  sh -c "cargo lambda build --release --arm64"
debug:
	cargo lambda watch
deploy:
	cd terraform && terraform apply -auto-approve
```

### size & runtime magic

- **binary:** 3.3 mb static executable (`strip`-ped)

- **sqlite + data:** 2.4 mb (**read-only**)

- **uncompressed total:** 5.7 mb → well under 50 mb lambda limit

these small sizes give us the 10 ms cold-start because we **avoid reaching s3, rds or any network dependency**.

## step 3—the terraform template in one file

copy the body below into `terraform/main.tf` (**hcl**).  this is beginner-friendly and easy to adapt for your **full-stack** app.

```
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

variable "region" {
  description = "aws region"
  default     = "us-east-1"
}

data "archive_file" "lambda_binary" {
  type        = "zip"
  source_file = "../target/lambda/coldstart_demo/bootstrap"
  output_path = "../bootstrap.zip"
}

resource "aws_lambda_function" "rust" {
  function_name = "rust-sqlite-showcase"
  role          = aws_iam_role.lambda_role.arn
  handler       = "bootstrap"
  runtime       = "provided.al2023"
  architectures = ["x86_64"]
  filename      = data.archive_file.lambda_binary.output_path
  memory_size   = 128   # smallest plan, enough
  timeout       = 3
  environment {
    variables = {
      log_level = "info"
    }
  }

  /* layer zero external deps -> zero network fetch during init! */
  layers = []

  /* push zip size facts */
  source_code_hash = data.archive_file.lambda_binary.output_base64sha256
}

resource "aws_iam_role" "lambda_role" {
  name = "rust_sqlite_lambda_role"
  assume_role_policy = jsonencode({
    version = "2012-10-17"
    statement = [{
      action    = "sts:assumerole"
      effect    = "allow"
      principal = { service = "lambda.amazonaws.com" }
    }]
  })
  managed_policy_arns = [
    "arn:aws:iam::aws:policy/service-role/awslambdabasicexecutionrole"
  ]
}

resource "aws_apigatewayv2_api" "http_api" {
  name          = "rust-sqlite"
  protocol_type = "http"
}

resource "aws_lambda_permission" "api_gw" {
  statement_id  = "allowexecutionfromapigateway"
  action        = "lambda:invokefunction"
  function_name = aws_lambda_function.rust.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_apigatewayv2_api.http_api.execution_arn}/*"
}

resource "aws_apigatewayv2_stage" "default" {
  api_id      = aws_apigatewayv2_api.http_api.id
  name        = "$default"
  auto_deploy = true
}

resource "aws_apigatewayv2_integration" "lambda" {
  api_id           = aws_apigatewayv2_api.http_api.id
  integration_type = "aws_proxy"
  integration_uri  = aws_lambda_function.rust.invoke_arn
}

resource "aws_apigatewayv2_route" "proxy" {
  api_id    = aws_apigatewayv2_api.http_api.id
  route_key = "any /{proxy+}"
  target    = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}

output "url" {
  value = aws_apigatewayv2_api.http_api.api_endpoint
}
```

### deploying

```
$ make build
$ make deploy

apply complete!
outputs:
  url = "https://xy123.execute-api.us-east-1.amazonaws.com"
```

paste the printed url in your browser and hit `/products/42`.  you should see sub-15 ms latency even on the very first run.

## secrets to reaching 10 ms

- **precompile**: your rust binary is statically linked, no additional runtime latency at boot.

- **bake the db**: read-only sqlite is in the same lambda package, so the lambda container already holds all data on disk.

- **slim runtime**: no provided.al2_ bloating; we use `provided.al2023` (64 bit only).

- **128 mb memory**: lambda assigns proportional cpu; small memory = less vcpu but acceptable for this micro-service.

- **layer free**: layers fetch zip from s3 and can add 200 ms rtt; we skip them entirely.

## monitoring your latency curve

instrument tracing in the rust binary and ship to datadog in one line by attaching the `awslambdabasicexecutionrole`.
use the following env variable for high-grade observability:

```
export dd_api_key=your-key
```

## when not to use serverless sqlite

- **writes-heavy**: sqlite in read-only mode makes inserts impossible without a new deployment.

- **multi-region**: data is local to the lambda zip; distribution must be handled manually.

- **hd video**: >4 gb read sets will bloat deploy zip and exceed lambda limits.

## next steps for the ambitious

  extend the template to **full-stack** mode:

- add regional deployment via cloudfront + edge lambdas.

- add [turso](https://turso.tech/) as a managed sqlite cloud to keep read-only datasets across many lambdas without code duplication.

- automate cli with `cargo lambda watch` for live reload on your laptop.

**happy _coding_!**
