What sandboxes are for
Some Lightdash features use an AI agent (Claude Code) that writes and runs code on your behalf. Today that’s:- AI writeback — the agent edits your dbt project (e.g. adds a metric or dimension),
runs
lightdash compileto validate it, and opens a pull request. - Data app generation — the agent generates and builds a small web app from a prompt.
Sandbox providers
The sandbox backend is pluggable. Lightdash talks to a provider-neutral interface, so the same feature code runs on whichever backend your deployment is configured for. You select the provider with theSANDBOX_PROVIDER environment variable.
LLM gateway egress
Data apps can route Claude or Codex model traffic through a corporate LLM gateway. The sandbox connects to that gateway independently from the Lightdash backend, so both network paths must be allowed.- E2B and Azure Sandboxes receive the coding agent’s effective gateway hostname in their per-sandbox egress allowlist, including any applicable Data app gateway override.
- AWS Lambda MicroVMs cannot apply Lightdash’s per-sandbox allowlist. If you use a restricted
LAMBDA_MICROVM_EGRESS_CONNECTOR_ARN, configure that connector to permit the gateway hostname. - Docker and Google Cloud Run use their existing runtime network path. Make sure its DNS and firewall policy can reach the gateway.
E2B (production default)
E2B runs each sandbox as a Firecracker microVM in E2B’s cloud. It’s the default — if you don’t setSANDBOX_PROVIDER, Lightdash uses E2B.
To use it you need an E2B account and API key, and the agent needs an Anthropic API key:
AWS Lambda MicroVMs (self-hosted production)
AWS Lambda MicroVMs run each sandbox as a Firecracker microVM inside your own AWS account, so untrusted agent code and your repository contents never leave your infrastructure. The microVMs have no public IP — your backend reaches each one through an AWS-managed endpoint that requires a short-lived per-microVM token — and you control their outbound network access (see Networking and IAM). This is the recommended sandbox provider for customers deploying Lightdash on AWS — it keeps the sandbox boundary inside your existing AWS account and avoids sending agent workloads or repository contents to a third-party service. Lightdash only needs the image ARN that AWS’screate-microvm-image returns. The
backend passes it to RunMicrovm and never builds, pulls, or otherwise touches ECR or S3
at runtime, so any pipeline that produces a codeArtifact zip AWS can build into a
MicroVM image works, with or without ECR.
Prerequisites
Provision these with your own IaC, in the region you set asLAMBDA_MICROVM_REGION and in
the AWS account your backend’s credentials belong to. That’s usually the account your
Lightdash backend already runs in. The backend uses the standard AWS SDK credential chain,
so the resources can sit in a separate account when the backend’s role is in that account,
for example an IRSA role there that trusts your cluster’s OIDC provider:
-
Two MicroVM images — one for data app generation and one for AI writeback (they
bundle different toolchains), built from the Dockerfiles in the Lightdash repo
(
sandboxes/data-apps/,sandboxes/ai-writeback/) and registered as a Lambda MicroVM image on the AWS-managedal2023base —ARM_64, 4 GB memory, with the agent’s/readyhook on port 8080. Each registration returns an image ARN for the config below. The recommended way to produce and refresh these ARNs issandboxes/microvm-agent/build-microvm-image.shin the Lightdash repo, which runs the whole pipeline: it builds the local Docker image for the feature you pass it (data-apporwriteback), provisions the ECR repository, build IAM role, and S3 staging bucket if they don’t exist yet, and pushes the local image to ECR so the registered image can buildFROMit. It then assembles a thinFROM <ecr-image>Dockerfile that adds the exec agent (sandboxes/microvm-agent/), zips it, and uploads it to S3 as the build’scodeArtifact— that zip is a thin wrapper around the ECR image, not the full application. It calls AWS’screate-microvm-image(orupdate-microvm-imagewhen the image already exists), polls the build to an active version, and prints the image ARN to set asLAMBDA_MICROVM_DATA_APP_IMAGE_ARNorLAMBDA_MICROVM_AI_WRITEBACK_IMAGE_ARN. Run it with./sandboxes/microvm-agent/build-microvm-image.sh data-appand./sandboxes/microvm-agent/build-microvm-image.sh writeback. Pushing to ECR is the script’s shortcut to avoid rebuilding the toolchain on every update, not something Lightdash requires. The data app Dockerfile copieslightdash-query-sdk.tgz, a generated artifact that a fresh checkout does not contain.build-microvm-image.shpacks it for you; building the Dockerfile directly instead needs./sandboxes/data-apps/pack-query-sdk.shfirst, using the toolchain listed in Build the gateway image under the Cloud Run provider. The writeback Dockerfile has no such prerequisite. -
Control-plane permissions on your backend’s existing IAM role — add
RunMicrovm,GetMicrovm,SuspendMicrovm,ResumeMicrovm,TerminateMicrovm,CreateMicrovmAuthToken, andlambda:PassNetworkConnector(the backend attaches the ingress and egress connectors on every launch). If you set an execution role (see Networking and IAM), also allowiam:PassRoleon that role’s ARN. Don’t add aniam:PassedToServicecondition: microVMs receive the role through a different service principal thanlambda.amazonaws.com, so the condition denies the pass.
create-microvm-image, which needs a build role (trusting
lambda.amazonaws.com) and an S3 location to stage the build context. These are
build-time only — not the S3 bucket Lightdash already uses for results and snapshots,
and the backend never touches them at runtime.
Configure the provider
Networking and IAM
Override these to tighten the network boundary or to give the microVM an IAM role:lambda.amazonaws.com with sts:AssumeRole and sts:TagSession.
The sandbox reaches the internet through the egress connector rather than AWS APIs, so the
role only needs CloudWatch Logs (logs:CreateLogGroup, logs:CreateLogStream,
logs:PutLogEvents). Keep it separate from your backend’s role, since the sandbox runs
agent-written code with whatever the execution role allows.
The microVM doesn’t inherit your backend’s AWS credentials. For data apps on Bedrock,
Lightdash passes BEDROCK_API_KEY or BEDROCK_ACCESS_KEY_ID and
BEDROCK_SECRET_ACCESS_KEY into the sandbox; with BEDROCK_USE_DEFAULT_CREDENTIALS=true
it passes none.
Upgrading
The sandbox runs whatever imageLAMBDA_MICROVM_DATA_APP_IMAGE_ARN and
LAMBDA_MICROVM_AI_WRITEBACK_IMAGE_ARN point to, so upgrading Lightdash alone doesn’t
change what runs inside it. Fixes to either image (Dockerfile changes, the exec agent in
sandboxes/microvm-agent/) only take effect once you rebuild the image:
- From a checkout of the new Lightdash version, remove the local sandbox image
(
lightdash-sandbox:localorlightdash-ai-writeback:local). The build script reuses an existing local image instead of rebuilding it. - Run
./sandboxes/microvm-agent/build-microvm-image.sh <data-app|writeback>. It pushes the image to ECR, registers a new version of the Lambda MicroVM image, and marks it active. - If the script prints a different image ARN from the one you have configured, update
LAMBDA_MICROVM_DATA_APP_IMAGE_ARNorLAMBDA_MICROVM_AI_WRITEBACK_IMAGE_ARNand restart the backend.
Azure Container Apps Sandboxes (self-hosted production)
Azure Container Apps Sandboxes run each sandbox as an isolated, microVM-class environment inside your own Azure subscription, so untrusted agent code and your repository contents never leave your infrastructure. Sandboxes have native suspend/resume (a full memory + disk snapshot with sub-second restore), which is what keeps multi-turn agent conversations fast. This is the recommended sandbox provider for customers deploying Lightdash on Azure — it keeps the sandbox boundary inside your existing Azure tenant and avoids sending agent workloads or repository contents to a third-party service.Azure Container Apps Sandboxes is currently an Azure preview feature. It requires a
Microsoft Entra ID account (personal Microsoft accounts aren’t supported), and its API
surface may change while in preview.
Prerequisites
Provision these in the same Azure subscription and region your Lightdash backend runs in, using theaca CLI or the
Sandboxes portal:
- A sandbox group per feature — one for data app generation and one for AI writeback
(they bundle different toolchains), plus an optional third for
Python execution. A sandbox group (
Microsoft.App/SandboxGroups) is the management boundary that holds a feature’s sandboxes and disk image. Give each group a Memory-mode auto-suspend lifecycle policy so idle sandboxes snapshot and scale to zero. - A disk image per group — build the two images from the Dockerfiles in the Lightdash
repo (
sandboxes/data-apps/,sandboxes/ai-writeback/), push them to a container registry (e.g. Azure Container Registry), and register each as a disk image in its sandbox group. Registration returns a disk image ID for the config below. (Unlike the AWS provider, there is no in-VM agent to build — Sandboxes expose a native command/file API.) The data app Dockerfile copieslightdash-query-sdk.tgz, a generated artifact that a fresh checkout does not contain — build and pack it intosandboxes/data-apps/first with./sandboxes/data-apps/pack-query-sdk.sh, using the toolchain listed in Build the gateway image under the Cloud Run provider. The writeback Dockerfile has no such prerequisite. For Python execution, build a third image fromsandboxes/python-runner/, which needs aDockerfile.localderived first — see Make sure the Python sandbox exists. - A workload identity with the data-plane role — grant your backend’s managed identity
the Container Apps SandboxGroup Data Owner role on each sandbox group. Lightdash uses
DefaultAzureCredential(workload identity on AKS, or the standard Azure credential chain) to authenticate — no client secret is configured here.
Configure the provider
Google Cloud Run Sandboxes (self-hosted, preview)
Use thegcp-cloud-run provider to run data app and Python execution
sandboxes in a Cloud Run service.
Lightdash connects to the service through an HTTP gateway included in the sandbox image.
Cloud Run Sandboxes is in public preview and currently supports data app generation and
Python execution. AI writeback is not supported.
Prerequisites
- A Google Cloud project with billing enabled.
- A recent version of the
gcloudCLI that supportsgcloud beta run deploy --sandbox-launcher. - External object storage configured for Lightdash.
- An Anthropic API key.
1. Enable the Google Cloud APIs
Enable Cloud Run, plus Artifact Registry if you push the gateway image there:2. Build the gateway image
Build the gateway image from a checkout of the Lightdash repository and push it to your own registry, such as Artifact Registry. To skip the build, you can get the published image from Docker Hub instead. You need:- A checkout of the Lightdash repository at the version you deploy.
- The Node version in
.nvmrcand the pnpm version in the rootpackage.json. - Repository dependencies installed with
sfw pnpm install --frozen-lockfilefrom the repository root. - Docker with Buildx running.
/app owned by root, as Cloud Run requires.
Or get the image from Docker Hub
Every Lightdash release from 2.362.0 onward publishes a prebuilt Cloud Run gateway image to Docker Hub:linux/amd64 only. It contains:
- The ComputeSDK Cloud Run gateway. The default command is
node /gateway/gateway.mjs. - The data app template in
/app, with its dependencies preinstalled./appis already owned by root, as Cloud Run requires. - The
@lightdash/query-sdkversion that matches the release. - Node, pnpm, and the coding agents used for generation.
:latest tag tracks the newest release.
Images exist for Lightdash 2.362.0 and later only. For an earlier version, upgrade
Lightdash or build the image yourself.
crane:
Get the image digest
Whichever option you use, get the digest of the image in your registry so you can deploy by digest:3. Deploy the gateway
Generate a shared secret and deploy the image from your registry, ideally by digest:SANDBOX_SECRET securely. You need the same value when configuring Lightdash.
Although the Cloud Run service allows unauthenticated requests, the gateway rejects any
sandbox operation that doesn’t include this secret.
Keep the service at one instance. Active sandboxes run inside that instance and are lost
if Cloud Run replaces it during a generation.
4. Configure Lightdash
Get the gateway URL:5. Restrict sandbox egress (recommended)
Sandbox traffic exits through the gateway instance’s network path, so it follows the service’s VPC egress setting and can be filtered with standard VPC networking. None of this is provisioned by Lightdash — you configure it yourself:- A subnet on the VPC network you’ll filter, in the same region as the gateway service.
-
A Cloud Router and Cloud NAT on that network. Once the service routes all traffic
through the VPC, outbound internet access only works via NAT — without it, sandboxes
and the gateway itself lose all egress. Reserve a static IP for the NAT if you want
a stable egress IP to allowlist on your side (for example, on your git host):
-
Direct VPC egress on the gateway service. This deploys a new revision, so any
active sandboxes are lost — do it while no generation is running:
All sandbox traffic now leaves through your VPC: its public egress IP is the NAT IP, and VPC firewall egress rules apply to it — a deny rule for a destination range blocks matching sandbox traffic while the rest keeps flowing.
-
Filter what sandboxes can reach. VPC firewall rules give you IP/CIDR-level
control. For a domain-level allowlist, add
Secure Web Proxy: create a
deny-all-egress firewall rule that only allows traffic to the proxy, and allowlist the
domains the sandboxes and the gateway need — at minimum
api.anthropic.comand your object storage endpoint. Standard data app generation and builds don’t download npm packages. Allowregistry.npmjs.org, or the hosts inLIGHTDASH_APP_DEPENDENCY_REGISTRY_HOSTS, only if your data apps use custom npm dependencies, which install at build time. Secure Web Proxy is an explicit proxy: clients must be configured to use it (bake proxy settings into the gateway image — sandboxes share its root filesystem), and the deny-all firewall drops anything that bypasses it.
6. Deploy the Python gateway (optional)
Python execution needs its own gateway service, built from the Python runner image. Get the image in one of two ways:-
Docker Hub — every release publishes
lightdash/lightdash-python-sandbox-cloud-run:<lightdash-version>(linux/amd64only;:latesttracks the newest release). -
Build it from a checkout of the Lightdash repository at the version you deploy. This
needs only Docker with Buildx and npm — no
pnpm install:
gcloud run services describe lightdash-python-sandbox-gateway ... as in
step 4, and set these on both the backend and scheduler (alongside
SANDBOX_PROVIDER=gcp-cloud-run):
pip install at runtime. Then turn Python execution on.
Each feature only needs its own gateway: with only the Python variables set, Python
execution works and data app generation fails with a configuration error, and vice versa.
Upgrading
When you upgrade Lightdash, redeploy each gateway (data app and, if deployed, Python) with the image for the new version. Rebuild from a checkout of the new version, or copy the new version’s tag from Docker Hub. Then get its digest and rerun the deploy command from step 3 (and step 6 for the Python gateway). Cloud Run always runs the deployed gateway image. It ignoresE2B_TEMPLATE_TAG, so
upgrading Lightdash alone doesn’t change what runs in the sandbox.
Redeploying replaces the single gateway instance, so active sandboxes are lost. Upgrade
during a quiet period. To roll back, redeploy the previous version’s tag or digest.
Local Docker provider (development)
For local development you can run sandboxes as plain Docker containers on your own machine — no E2B account required. This is the recommended way to work on or try the AI features locally. It uses the same images E2B builds, but as plain local Docker images. Each feature uses a separate image (different toolchains), mirroring the E2B templates:Prerequisites
- Docker running locally, with the daemon reachable from the Lightdash backend.
- S3-compatible object storage configured (locally this is MinIO). Suspended-sandbox snapshots are tarred to object storage so a conversation survives the container being destroyed — see external object storage.
- An Anthropic API key (
ANTHROPIC_API_KEY) for the agent.
Setup
-
Build the local sandbox images (each builds from
sandboxes/<feature>/):Build only the images for the features you use. These are large (the writeback image bundles dbt, the Lightdash CLI and Claude Code) and only need rebuilding when the sandbox toolchain changes. -
Point Lightdash at the Docker provider:
-
Restart the backend and the scheduler so both pick up the new environment. Data app
generation runs in the scheduler worker, so a stale
SANDBOX_PROVIDERthere will keep it on E2B. (With PM2, a plainrestartreuses the cached env — delete and re-start the processes, or restart with--update-env, to actually reload the env file.)
Python execution
Python execution runs Python over the results of other queries in a pipeline. Lightdash writes the referenced results into a fresh sandbox, runs the code, reads the output back, and destroys the sandbox. The sandbox has no credentials and no outbound network access. Lightdash never suspends it, so snapshot lifecycle settings don’t apply. The sandbox image includes pandas, numpy, scipy, pyarrow, scikit-learn, and polars. Your code must return a pandas DataFrame.Supported providers
Python execution runs on the E2B, Azure Container Apps Sandboxes, Google Cloud Run, and Local Docker providers. It doesn’t work onlambda-microvm.
Make sure the Python sandbox exists
-
E2B — Lightdash launches
E2B_PYTHON_TEMPLATE_NAME(defaultlightdash-python-runner) atE2B_PYTHON_TEMPLATE_TAG(default: your Lightdash version). YourE2B_API_KEYmust have access to the template at that tag. To build the template in your own E2B team, check out the Lightdash repository at your version, run the build fromsandboxes/python-runner/, and then set both variables to the template you built:When you upgrade Lightdash, rebuild or retag the template so a build exists at the new version’s tag, or pinE2B_PYTHON_TEMPLATE_TAG. -
Google Cloud Run — deploy the separate Python gateway and set
GCP_CLOUD_RUN_PYTHON_SANDBOX_URLandGCP_CLOUD_RUN_PYTHON_SANDBOX_SECRET. See Deploy the Python gateway. -
Azure Container Apps Sandboxes — build the image from
sandboxes/python-runner/and register it as a disk image in a dedicated Python sandbox group. The directory ships an E2B Dockerfile, so first deriveDockerfile.localfrom it with./sandboxes/python-runner/build-local-image.sh(you can ignore the local image it then builds). Azure sandboxes run onlinux/amd64, so build for that platform and register the result:SetAZURE_SANDBOXES_PYTHON_GROUPto the group andAZURE_SANDBOXES_PYTHON_DISK_IMAGEto the disk image ID fromdisk list, and grant the backend identity the same data-plane role on the group. See Azure Container Apps Sandboxes. -
Local Docker — build
lightdash-python-runner:localwith./sandboxes/python-runner/build-local-image.sh, or setSANDBOX_PYTHON_DOCKER_IMAGEto your own tag. See Local Docker provider.
Turn it on
Python execution is part of the composer, gated by thecompose-sql-runner feature flag,
which is off by default. To turn it on, set
LIGHTDASH_ENABLE_FEATURE_FLAGS
on the backend and the scheduler:
Snapshot lifecycle
Every turn suspends its own sandbox, so in steady state nothing sits idle. Two timers configure the cloud-side idle policy as a backstop — when to auto-suspend a sandbox left running and when to auto-terminate a suspended one:SANDBOX_IDLE_TIMEOUT_MS feeds the auto-suspend policy on both the Lambda MicroVMs and
Azure Sandboxes providers (for Azure it sets each sandbox’s Memory-mode auto-suspend
interval). SANDBOX_SNAPSHOT_RETENTION_MS is read only by Lambda MicroVMs — on Azure,
suspended-sandbox retention is governed by the sandbox group’s own auto-delete policy.
E2B manages idle sandboxes itself, and the Docker and Cloud Run providers have
no cloud-side idle handling — both persist suspend-time snapshots to object storage and
destroy the sandbox, so nothing idles in the first place.