Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git
.github
.vscode
.conductor
node_modules
.next
out
build
coverage
test-results
readme-assets
.env
.env.*
*.md
Dockerfile
.dockerignore
21 changes: 15 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
### Domain for the E2B cluster.
### Resolves infra-api (`https://api.<domain>`) and dashboard-api
### (`https://dashboard-api.<domain>`) unless overridden below.
NEXT_PUBLIC_E2B_DOMAIN=e2b.dev
PUBLIC_E2B_DOMAIN=e2b.dev

### =================================
### OPTIONAL ENVIRONMENT VARIABLES
Expand All @@ -16,13 +16,22 @@ NEXT_PUBLIC_E2B_DOMAIN=e2b.dev
### and sign-out is hidden.
# E2B_API_KEY=e2b_your_team_api_key

### Explicit API base URLs (override the NEXT_PUBLIC_E2B_DOMAIN resolution;
### Runtime API base URLs (override the cluster domain resolution;
### useful for local infra development).
# NEXT_PUBLIC_INFRA_API_URL=http://localhost:3000
# NEXT_PUBLIC_DASHBOARD_API_URL=http://localhost:3001
# E2B_INFRA_API_URL=http://127.0.0.1:3000
# E2B_DASHBOARD_API_URL=http://127.0.0.1:3010

### Optional sandbox traffic base URL for local development proxies.
# NEXT_PUBLIC_E2B_SANDBOX_URL=http://sandbox.lvh.me:3002
### Base URL the BROWSER uses to reach sandboxes (terminal and filesystem
### inspector). Set this explicitly for local sandbox proxies. It must be
### reachable from both the browser and the server —
### the loopback below works only when the two are the same machine.
# PUBLIC_SANDBOX_URL=http://127.0.0.1:3002
### With no sandbox URL, the SDK uses the cluster domain.
### Request headers never select this URL.

### Set to "false" when the dashboard is served over plain http (a LAN address
### or an IP), or the browser drops the api key cookie and the key form loops.
# DASHBOARD_COOKIE_SECURE=false

### OpenTelemetry (disabled unless the endpoint is set).
# OTEL_SERVICE_NAME=e2b-dashboard
Expand Down
60 changes: 60 additions & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Nothing else in CI builds the image, so a break in the Docker build would
# otherwise go unnoticed until someone builds it by hand. This job runs on the
# files that can break it, not on every PR — a full Next build in Docker is
# minutes, and application changes are already covered by Test / Code Quality.
name: Container

on:
push:
branches: [main]
paths:
- Dockerfile
- .dockerignore
- next.config.ts
- tsconfig.json
- package.json
- bun.lock
- scripts/check-app-env.ts
- scripts/container-smoke.sh
- src/lib/env.ts
- src/instrumentation.ts
- src/core/server/runtime-config.ts
- src/configs/cookies.ts
- .github/workflows/container.yml
pull_request:
branches: [main]
paths:
- Dockerfile
- .dockerignore
- next.config.ts
- tsconfig.json
- package.json
- bun.lock
- scripts/check-app-env.ts
- scripts/container-smoke.sh
- src/lib/env.ts
- src/instrumentation.ts
- src/core/server/runtime-config.ts
- src/configs/cookies.ts
- .github/workflows/container.yml
workflow_dispatch:

env:
FORCE_COLOR: "1"
CLICOLOR_FORCE: "1"

permissions:
contents: read

jobs:
smoke:
name: Build and Smoke-Test the Image
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Build the image and check the responses it serves
run: ./scripts/container-smoke.sh
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
needs: unit-tests
env:
NEXT_PUBLIC_E2B_DOMAIN: e2b-test.dev
PUBLIC_E2B_DOMAIN: e2b-test.dev

steps:
- name: Checkout code
Expand Down
58 changes: 58 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Three stages: Bun resolves the dependencies (bun.lock is the lockfile), Node
# runs the Next build, Node serves. The runtime stage carries only Next's
# standalone output, so the full dependency tree never ships in the image.
#
# The build runs under Node, not Bun: `bun run build` forks Next's page-data
# workers, and Bun's CommonJS interop throws "Expected CommonJS module to have
# a function wrapper" on the webpack output those workers load.
#
# The build fetches three Google Fonts families through next/font/google
# (src/app/fonts.ts): it needs outbound HTTPS to fonts.googleapis.com and
# fonts.gstatic.com, and fails there in an air-gapped environment.
FROM oven/bun:1.2.20 AS deps

WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

FROM node:22-bookworm-slim AS builder

WORKDIR /app

# Only to run the prebuild env check, which is a TypeScript entrypoint.
COPY --from=deps /usr/local/bin/bun /usr/local/bin/bun
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1

# Page-data collection imports API clients. This build-only domain is not
# carried into the runtime image; each installation must provide its own.
RUN PUBLIC_E2B_DOMAIN=build.invalid bun scripts/check-app-env.ts
RUN PUBLIC_E2B_DOMAIN=build.invalid node node_modules/next/dist/bin/next build --webpack

FROM node:22-bookworm-slim AS runtime

WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# server.js reads PORT (default 3000) and HOSTNAME (default 0.0.0.0). The
# default is 3001 so the dashboard does not land on 3000, which an E2B install
# already uses for its API when both share a host network.
ENV PORT=3001
ENV HOSTNAME=0.0.0.0

# Reported as service.version on OTEL traces (src/instrumentation.node.ts).
ARG BUILD=dev
ENV BUILD=${BUILD}

COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
COPY --from=builder --chown=node:node /app/public ./public

USER node
EXPOSE 3001

CMD ["node", "server.js"]
85 changes: 83 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,58 @@ Authentication is a single **team API key**:
- Visiting `/` shows a form to enter the key. It is validated against infra-api and stored in an httpOnly `e2b_api_key` cookie. All upstream calls happen server-side with the `X-API-Key` header — the key never reaches client JavaScript.
- Alternatively, set the `E2B_API_KEY` environment variable to pre-authenticate the whole deployment (single-user mode; the key form and sign-out are hidden).

### Configuration

| Variable | Read | Purpose |
|---|---|---|
| `PUBLIC_E2B_DOMAIN` | runtime | Required E2B cluster domain; used by the SDK and to derive `https://api.<domain>` and `https://dashboard-api.<domain>` |
| `PUBLIC_SANDBOX_URL` | per request | Optional sandbox traffic base URL, reachable from both the browser and server |
| `E2B_INFRA_API_URL` / `E2B_DASHBOARD_API_URL` | server start | Optional server-side API URLs; override domain-derived URLs |
| `DASHBOARD_COOKIE_SECURE` | server start | `false` only for a plain-http install; the API key cookie then travels unencrypted. Defaults to secure in production builds |

Set `PUBLIC_E2B_DOMAIN` when starting the container. Next does not give
`PUBLIC_` any special behavior: the server explicitly reads these values at
runtime. Restart the container and reload open pages after changing its
configuration. Missing or blank domains stop startup.

Legacy variables no longer act as fallbacks. Rename them before upgrading;
validation reports the replacement for each deprecated variable that is still
set, even if the new name is also present.

| Deprecated variable | Replacement |
|---|---|
| `NEXT_PUBLIC_E2B_DOMAIN` | `PUBLIC_E2B_DOMAIN` |
| `NEXT_PUBLIC_INFRA_API_URL` | `E2B_INFRA_API_URL` |
| `NEXT_PUBLIC_DASHBOARD_API_URL` | `E2B_DASHBOARD_API_URL` |
| `NEXT_PUBLIC_E2B_SANDBOX_URL` / `E2B_SANDBOX_URL` | `PUBLIC_SANDBOX_URL` |

API URL overrides remain optional; without them the domain determines both
API URLs. An absent or blank `PUBLIC_SANDBOX_URL` lets the SDK use domain
routing.

Every API or sandbox URL must include `http://` or `https://`. The schema in
`src/lib/env.ts` validates configuration for development, builds, and
server startup, even when telemetry is disabled. Invalid values stop startup
and name the variable. The cookie flag accepts `true` or `false` (case-insensitive, with
surrounding whitespace ignored); an empty value keeps the default.

The dashboard's Server Component layout resolves **only the domain and
sandbox URL** and passes them as props to a client `ClientConfigProvider`.
The terminal and filesystem inspector read this provider on their first
render, without a separate config request. API endpoints and team credentials
stay on the server. Both public settings are visible to browser users and
must contain no secrets.

For a local sandbox proxy, explicitly set `PUBLIC_SANDBOX_URL`, for example
`http://127.0.0.1:3002` when the browser and server run on the same machine.
Use an address reachable from both the browser and server. When a sandbox
URL is unset, the SDK uses domain-based routing, including when
`E2B_INFRA_API_URL` is set.

Server-side sandbox calls, such as terminal PTY cleanup, use the same domain
and sandbox URL resolution. `Host`, `X-Forwarded-Host`, and
`X-Forwarded-Proto` never determine sandbox destinations.

## Features

- **Sandboxes**: paginated live list, per-sandbox monitoring (CPU/memory/disk), logs, filesystem inspector, and an in-browser terminal
Expand Down Expand Up @@ -57,8 +109,8 @@ bun install
3. Set up environment variables
```bash
cp .env.example .env
# set NEXT_PUBLIC_E2B_DOMAIN (or explicit NEXT_PUBLIC_INFRA_API_URL /
# NEXT_PUBLIC_DASHBOARD_API_URL) to point at your infrastructure
# set PUBLIC_E2B_DOMAIN (and optionally E2B_INFRA_API_URL /
# E2B_DASHBOARD_API_URL) to point at your infrastructure
```

4. Start the development server
Expand All @@ -75,6 +127,35 @@ bun run build
bun run start
```

### Run it in a container

The repository builds a self-contained image: Bun resolves the dependencies,
Node runs the Next build, and Node serves the standalone output; the runtime
stage carries no dev dependencies.

```bash
docker build -t e2b-dashboard .
docker run --rm -p 3001:3001 \
-e PUBLIC_E2B_DOMAIN=your-domain.com \
e2b-dashboard
```

- `PORT` (default `3001`) and `HOSTNAME` (default `0.0.0.0`) are read by the
server at start. The default keeps the dashboard clear of port 3000, which
an E2B API already uses when both share a host network.
- `PUBLIC_E2B_DOMAIN` configures the cluster at container start, so the same
image can serve different installations. Use `PUBLIC_SANDBOX_URL` when the
default SDK routing does not fit your deployment.
- The image builds without installation settings. Its temporary build domain
is not carried into the runtime image, so a container started without
`PUBLIC_E2B_DOMAIN` fails validation.
- The build needs outbound HTTPS for the three Google Fonts families in
`src/app/fonts.ts`; an air-gapped build fails there.
- `GET /api/health` reports dashboard-api's health and answers 503 while
dashboard-api is unreachable, so use `GET /` as the container liveness
check.
- `scripts/container-smoke.sh` builds the image and asserts those responses.

## Scripts

| Command | Description |
Expand Down
4 changes: 4 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const browserNodeModuleStubs = {
const config: NextConfig = {
reactStrictMode: true,
reactCompiler: true,
// Emits .next/standalone: a server plus only the traced dependencies, which
// is what the container image runs. `next start` still works from .next for
// local previews, and platform builds ignore this output.
output: 'standalone',
experimental: {
useCache: true,
turbopackFileSystemCacheForDev: true,
Expand Down
Loading
Loading