feat: Sandboxed function execution via FUNCTIONS_DRIVER=docker

Each invoke runs in an ephemeral locked-down container — no network,
read-only rootfs, 128MiB/0.5CPU/64-pid caps, non-root, cap-drop ALL.
Source travels as base64 in the container env and materializes in the
tmpfs, so no bind mount is needed (Docker Desktop / remote daemons work).
Timeout force-removes the named container.

exec driver stays the default; the trust-model docs now describe both.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-24 20:45:33 +02:00
1 parent edef002beb
commit 9ae1c22af0
8 files changed
+246 -9

No files matched your search

+16 -6
View File
@@ -47,7 +47,8 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
| `S3_ACCESS_KEY_ID` / `S3_SECRET_ACCESS_KEY` | S3 credentials | with `s3` |
| `S3_PREFIX` / `S3_PATH_STYLE` | optional key prefix; path-style URLs (default `true`, needed by MinIO/Garage — set `false` for AWS virtual-hosted) | no |
| `S3_PUBLIC_ENDPOINT` | client-facing S3 endpoint used for presigned URLs when `S3_ENDPOINT` is internal-only (e.g. `http://minio:9000`); signatures bind the public host | no |
| `FUNCTIONS_RUNTIME` / `FUNCTIONS_TIMEOUT_SECONDS` / `FUNCTIONS_MAX_OUTPUT_BYTES` | Functions: runtime binary (`auto` resolves `bun`/`deno` from the backend's PATH — the backend image must ship one, or set this to its path), per-run timeout (default 30 s), captured-output cap (default 64 KiB) | no |
| `FUNCTIONS_DRIVER` | Function execution driver: `exec` (default — runtime binary on the backend host; code runs with backend privileges) or `docker` — each invoke runs in an ephemeral locked-down container (no network, read-only fs, 128 MiB, non-root). Docker mode needs `/var/run/docker.sock` mounted into the backend (see the commented volume in `docker-compose.yml`) and the runtime images pulled (`oven/bun`, `denoland/deno`). Source code is capped at 256 KiB under the docker driver | no |
| `FUNCTIONS_RUNTIME` / `FUNCTIONS_TIMEOUT_SECONDS` / `FUNCTIONS_MAX_OUTPUT_BYTES` | Functions: runtime binary (`auto` resolves `bun`/`deno` — exec driver only; docker driver uses the matching official image), per-run timeout (default 30 s), captured-output cap (default 64 KiB) | no |
| `AUTH_ADMIN_EMAILS` | comma-separated emails promoted to auth admin on boot | no |
| `USER_RATE_LIMIT_PER_MINUTE` / `API_KEY_RATE_LIMIT_PER_MINUTE` | per-identity API rate limits (defaults 240 / 600) | no |
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASSWORD` / `SMTP_SECURE` / `MAIL_FROM` | transactional mail; without it verification/reset emails go nowhere | production |
@@ -140,11 +141,20 @@ variables above (AWS, MinIO, Garage, R2 all work).
## Functions trust model
Function code runs as a child process of the backend — same host, same
privileges, no sandbox. Treat every project member who can edit a function
as holding a shell on the backend host. If that is unacceptable for your
deployment, do not enable untrusted editors; isolated runtimes are on the
roadmap.
Two drivers, two trust levels:
- **`exec` (default)** — function code runs as a child process of the
backend: same host, same privileges, no sandbox. Treat every project
member who can edit a function as holding a shell on the backend host.
Appropriate for single-team self-hosted deployments.
- **`docker`** — each invoke runs in an ephemeral container: no network,
read-only rootfs, 128 MiB memory, half a CPU, 64 pids, non-root, all
capabilities dropped. Requires the Docker socket mounted into the
backend (commented volume in `docker-compose.yml`) — note the socket is
itself root-equivalent on the host, so this isolates *function code*,
not operators. Source code capped at 256 KiB. For hostile multi-tenant
workloads, a dedicated microVM runtime (Firecracker/gVisor) remains the
roadmap item.
## Observability