Skip to content

Commit 8b92a9c

Browse files
xesrevinuGrok
andcommitted
feat(deploy): slim the celld image and migrate without drizzle-kit
turbo prune the Worker graph, drop oxlint/alchemy/wrangler from the runtime, and apply Postgres migrations with drizzle-orm so the image does not need drizzle-kit. Co-Authored-By: Grok <grok@x.ai>
1 parent a23acfd commit 8b92a9c

8 files changed

Lines changed: 117 additions & 32 deletions

File tree

.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ target
88
**/target
99
apps/api/.data
1010
apps/api/.data/**
11+
apps/ios
12+
apps/web/dist
13+
**/.celld
14+
**/.wrangler
15+
**/.turbo
1116
.env
1217
.env.*
1318
**/.env

deploy/celld-self-host/compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ services:
186186
set -euo pipefail
187187
export PATH="/usr/local/bin:/root/.bun/bin:$$PATH"
188188
cd /app
189-
bun run --cwd packages/db db:migrate
189+
bun run --cwd packages/db db:migrate:pg
190190
bun run --cwd packages/clickhouse-cli start apply \
191191
--url="$$CLICKHOUSE_URL" --user="$$CLICKHOUSE_USER" \
192192
--password="$$CLICKHOUSE_PASSWORD" --database="$$CLICKHOUSE_DATABASE"

deploy/celld-self-host/docker/Dockerfile.celld

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,80 @@
1-
# celld Worker runtime. Same Maple Worker sources as Cloudflare; no alchemy.
2-
# Build from repo root: docker build -f deploy/celld-self-host/docker/Dockerfile.celld .
1+
# celld Worker runtime. Production graph only: api + electric-sync + alerting
2+
# (+ clickhouse-cli / db for the migrate job). Build from repo root:
3+
# docker build -f deploy/celld-self-host/docker/Dockerfile.celld .
4+
35
FROM oven/bun:1.4.0 AS base
46
WORKDIR /app
57

8+
FROM base AS pruner
9+
COPY . .
10+
RUN bunx turbo prune \
11+
@maple/api \
12+
@maple/alerting \
13+
@maple/electric-sync \
14+
@maple/clickhouse-cli \
15+
--docker --out-dir /app/out
16+
17+
FROM base AS deps
18+
COPY --from=pruner /app/out/json/ ./
19+
COPY --from=pruner /app/out/bun.lock ./bun.lock
20+
COPY bunfig.toml ./
21+
COPY patches ./patches
22+
# turbo prune keeps the root package.json, including oxlint/alchemy/knip.
23+
# Those are not Worker runtime. Drop them before bun install.
24+
RUN bun -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); p.devDependencies={}; p.scripts={}; fs.writeFileSync("package.json", JSON.stringify(p,null,2));'
25+
RUN bun install --ignore-scripts || bun install --ignore-scripts
26+
27+
FROM deps AS build
28+
COPY --from=pruner /app/out/full/ ./
29+
# `out/full` restores the original root package.json; strip tooling again.
30+
RUN bun -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); p.devDependencies={}; p.scripts={}; fs.writeFileSync("package.json", JSON.stringify(p,null,2));'
31+
# Re-install with full sources so workspace bins (tsdown) resolve, then emit
32+
# dist/ for packages whose package.json exports point at dist/, not src/.
33+
RUN bun install --ignore-scripts || bun install --ignore-scripts
34+
RUN bun run --cwd lib/clickhouse-builder build && \
35+
bun run --cwd packages/effect-sdk build
36+
37+
# Drop workspace devDependencies (wrangler/workerd/miniflare/vitest).
38+
# Postgres migrate uses packages/db/scripts/migrate-pg.ts (drizzle-orm), not drizzle-kit.
39+
FROM build AS prod
40+
RUN rm -rf node_modules && bun install --production --ignore-scripts
41+
42+
FROM oven/bun:1.4.0-slim AS runtime
43+
WORKDIR /app
44+
645
ARG CELLD_VERSION=v0.4.0
7-
ARG TARGETARCH=arm64
46+
ARG TARGETARCH
47+
ARG ESBUILD_VERSION=0.24.2
848

949
RUN apt-get update \
1050
&& apt-get install -y --no-install-recommends ca-certificates curl gzip \
1151
&& rm -rf /var/lib/apt/lists/*
1252

1353
RUN set -euo pipefail; \
14-
case "$TARGETARCH" in \
15-
amd64) asset=celld-x86_64-unknown-linux-gnu.gz ;; \
16-
arm64) asset=celld-aarch64-unknown-linux-gnu.gz ;; \
17-
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \
54+
arch="${TARGETARCH:-}"; \
55+
if [ -z "$arch" ]; then \
56+
case "$(uname -m)" in \
57+
aarch64|arm64) arch=arm64 ;; \
58+
x86_64|amd64) arch=amd64 ;; \
59+
*) echo "unsupported arch $(uname -m)" >&2; exit 1 ;; \
60+
esac; \
61+
fi; \
62+
case "$arch" in \
63+
amd64) asset=celld-x86_64-unknown-linux-gnu.gz; plat=linux-x64 ;; \
64+
arm64) asset=celld-aarch64-unknown-linux-gnu.gz; plat=linux-arm64 ;; \
65+
*) echo "unsupported TARGETARCH=$arch" >&2; exit 1 ;; \
1866
esac; \
1967
curl -fsSL "https://github.com/denoland/celld/releases/download/${CELLD_VERSION}/${asset}" \
2068
| gzip -d > /usr/local/bin/celld; \
2169
chmod +x /usr/local/bin/celld; \
22-
celld --version
23-
24-
COPY package.json bun.lock bunfig.toml turbo.json ./
25-
COPY apps ./apps
26-
COPY packages ./packages
27-
COPY lib ./lib
28-
COPY patches ./patches
29-
30-
ARG ESBUILD_VERSION=0.24.2
31-
RUN set -euo pipefail; \
32-
case "$TARGETARCH" in \
33-
amd64) plat=linux-x64 ;; \
34-
arm64) plat=linux-arm64 ;; \
35-
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \
36-
esac; \
3770
curl -fsSL "https://registry.npmjs.org/@esbuild/${plat}/-/${plat}-${ESBUILD_VERSION}.tgz" \
3871
| tar -xz -C /tmp; \
3972
install -m 0755 /tmp/package/bin/esbuild /usr/local/bin/esbuild; \
73+
rm -rf /tmp/package; \
74+
celld --version; \
4075
esbuild --version
4176

42-
# Workspace install can flake on npm (pglite/alchemy are unused at celld runtime).
43-
RUN bun install || bun install
44-
77+
COPY --from=prod /app /app
4578
COPY deploy/celld-self-host/docker/entrypoint-celld.sh /entrypoint-celld.sh
4679
RUN chmod +x /entrypoint-celld.sh
4780

deploy/celld-self-host/docker/entrypoint-celld.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
#!/usr/bin/env bash
1+
#!/bin/sh
22
# Production celld: deploy the Worker into the fleet bucket, then serve it.
33
# Not `celld dev` — that uses PROJECT/.celld/dev and shares nothing with prod.
4-
set -euo pipefail
4+
set -eu
55

66
APP_DIR="${CELLD_APP_DIR:?CELLD_APP_DIR required}"
77
PORT="${CELLD_PORT:?CELLD_PORT required}"
@@ -14,7 +14,7 @@ REGION="${AWS_REGION:-us-east-1}"
1414
cd "$APP_DIR"
1515
export PATH="/usr/local/bin:/root/.bun/bin:${PATH}"
1616
export CELLD_ESBUILD="${CELLD_ESBUILD:-$(command -v esbuild)}"
17-
if [[ -z "${CELLD_ESBUILD}" ]]; then
17+
if [ -z "${CELLD_ESBUILD}" ]; then
1818
echo "celld-entrypoint: esbuild not on PATH" >&2
1919
exit 1
2020
fi
@@ -27,11 +27,12 @@ mkdir -p "$CELLD_WATCH"
2727
echo "celld-entrypoint: deploy $CONFIG$BUCKET ($ENDPOINT)"
2828
"$CELLD_BIN" deploy "$CONFIG" --bucket "$BUCKET" --endpoint "$ENDPOINT" --region "$REGION"
2929

30-
echo "celld-entrypoint: listen 0.0.0.0:${PORT}"
30+
LISTEN_HOST="${CELLD_LISTEN_HOST:-0.0.0.0}"
31+
echo "celld-entrypoint: listen ${LISTEN_HOST}:${PORT}"
3132
exec "$CELLD_BIN" \
3233
--bucket "$BUCKET" \
3334
--endpoint "$ENDPOINT" \
3435
--region "$REGION" \
35-
--listen "0.0.0.0:${PORT}" \
36+
--listen "${LISTEN_HOST}:${PORT}" \
3637
--internal-listen "127.0.0.1:0" \
3738
--trust-forwarded-headers

deploy/celld-self-host/k8s/migrate.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ spec:
2828
set -euo pipefail
2929
export PATH="/usr/local/bin:/root/.bun/bin:$PATH"
3030
cd /app
31-
bun run --cwd packages/db db:migrate
31+
bun run --cwd packages/db db:migrate:pg
3232
bun run --cwd packages/clickhouse-cli start apply \
3333
--url="$CLICKHOUSE_URL" \
3434
--user="$CLICKHOUSE_USER" \

docs/celld-self-host.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,29 @@ deploy/celld-self-host/
282282
k8s/
283283
```
284284

285+
### Images
286+
287+
`Dockerfile.celld` is a production graph, not a copy of the monorepo:
288+
289+
1. `turbo prune @maple/api @maple/alerting @maple/electric-sync @maple/clickhouse-cli`
290+
2. strip root tooling (`oxlint` / `alchemy` / `knip`)
291+
3. build `clickhouse-builder` + `effect-sdk` `dist/`
292+
4. `bun install --production` (no wrangler / workerd / vitest)
293+
5. slim runtime: `oven/bun:1.4.0-slim` + celld + esbuild
294+
295+
Migrate uses `bun run --cwd packages/db db:migrate:pg` (`drizzle-orm` migrator),
296+
not `drizzle-kit`, so the runtime image does not need that devDependency.
297+
298+
Measured on arm64 (one image; api/sync/alerting share layers):
299+
300+
| Image | Approx |
301+
| ----------------------------- | -------------------------------------------------------- |
302+
| `maple-celld` | 559 MB (was 3.35 GB with a full-workspace `bun install`) |
303+
| `maple-web` | 75 MB |
304+
| `maple-pg-ws-proxy` | 185 MB (`bun` slim base) |
305+
| `maple-otel` | 29 MB |
306+
| ClickHouse / Electric / Caddy | upstream |
307+
285308
## VPS notes
286309

287310
A VPS bring-up is the data plane plus one celld process per public Worker:

packages/db/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"test": "vitest run --passWithNoTests",
1515
"db:generate": "drizzle-kit generate --config ./drizzle.config.ts",
1616
"db:migrate": "drizzle-kit migrate --config ./drizzle.config.ts",
17+
"db:migrate:pg": "bun scripts/migrate-pg.ts",
1718
"db:ensure-electric-publication": "bun scripts/ensure-electric-publication.ts",
1819
"db:push": "drizzle-kit push --config ./drizzle.config.ts",
1920
"db:studio": "drizzle-kit studio --config ./drizzle.config.ts",

packages/db/scripts/migrate-pg.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Apply bundled drizzle SQL to a real Postgres URL.
3+
* Used by the celld self-host migrate Job so the runtime image does not need
4+
* drizzle-kit (a db devDependency). Local/CI still use `db:migrate`.
5+
*/
6+
import { dirname, resolve } from "node:path"
7+
import { fileURLToPath } from "node:url"
8+
import { drizzle } from "drizzle-orm/postgres-js"
9+
import { migrate } from "drizzle-orm/postgres-js/migrator"
10+
import postgres from "postgres"
11+
12+
const url = process.env.DATABASE_URL?.trim() || process.env.MAPLE_PG_URL?.trim()
13+
if (!url) {
14+
console.error("migrate-pg: DATABASE_URL or MAPLE_PG_URL is required")
15+
process.exit(1)
16+
}
17+
18+
const migrationsFolder = resolve(dirname(fileURLToPath(import.meta.url)), "../drizzle")
19+
const sql = postgres(url, { max: 1 })
20+
await migrate(drizzle(sql), { migrationsFolder })
21+
await sql.end({ timeout: 5 })
22+
console.log("migrate-pg: applied", migrationsFolder)

0 commit comments

Comments
 (0)