Skip to content
Open
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
3 changes: 3 additions & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const nextConfig: NextConfig = {
return [host, host ? `*.${apex(host)}` : "", "localhost", "127.0.0.1"].filter(Boolean);
})(),
experimental: {
turbopackMemoryLimit: process.env.DOABLE_BUILD_MEMORY_MB
? Number(process.env.DOABLE_BUILD_MEMORY_MB) * 1024 * 1024
: undefined,
serverActions: {
bodySizeLimit: "2mb",
},
Expand Down
13 changes: 13 additions & 0 deletions deployment/docker/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@
{$DOABLE_SITE:localhost} {
tls {$DOABLE_TLS:internal}

# Compress preview modules and static assets; leave SSE and API streams alone.
@compressible_assets path /_next/static/* /preview/* /api/preview/* /brand/* /sites/*
encode @compressible_assets zstd gzip {
match {
header Content-Type text/javascript*
header Content-Type application/javascript*
header Content-Type text/css*
header Content-Type text/html*
header Content-Type application/json*
header Content-Type image/svg+xml*
}
}

# ─── OTLP browser tracing exporter → Next.js proxy (web container) ──
# apps/web/src/app/api/otlp/[...path]/route.ts forwards to the api
# container's /internal/otlp/* receiver. MUST come before the
Expand Down
43 changes: 18 additions & 25 deletions deployment/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,17 @@ COPY . .
# Build packages that compile cleanly (docore, dovault, web)
RUN pnpm --filter=docore run build || true
RUN pnpm --filter=dovault run build || true
RUN pnpm --filter=@doable/web run build
# Only the web image needs Next.js output. API/WS/migrate copy the shared
# packages without recompiling the frontend on every backend source change.
FROM build AS web-build

# Optional budget for source builds on memory-constrained hosts.
# Leave empty to retain the existing Node/Next.js defaults.
ARG WEB_BUILD_MEMORY_MB=
RUN if [ -n "$WEB_BUILD_MEMORY_MB" ]; then \
NODE_OPTIONS="--max-old-space-size=$WEB_BUILD_MEMORY_MB" \
DOABLE_BUILD_MEMORY_MB="$WEB_BUILD_MEMORY_MB" pnpm --filter=@doable/web run build; \
else pnpm --filter=@doable/web run build; fi

# --- API service ---
FROM base AS api
Expand All @@ -100,9 +110,7 @@ WORKDIR /app
# libs Chrome crashes at first thumbnail attempt with
# `libnspr4.so: cannot open shared object file`. Same dep list as
# deployment/server-setup.sh:298-322 so docker + bare-metal stay in
# lockstep. The bundled Chromium itself downloads in the next layer
# (PUPPETEER_SKIP_CHROMIUM_DOWNLOAD is intentionally unset for the api
# stage).
# lockstep. The distro Chromium below is also the runtime executable.
#
# `chromium` is the distro-native browser we point Puppeteer at via
# PUPPETEER_EXECUTABLE_PATH below. On Apple Silicon hosts running native
Expand Down Expand Up @@ -155,29 +163,14 @@ RUN mkdir -p /app/services/api/projects /app/services/api/thumbnails /data/sites
&& ln -sf services/api/thumbnails /app/thumbnails \
&& chown -h node:node /app/projects /app/thumbnails

# Download the puppeteer-bundled Chrome into the image so first-thumbnail
# capture doesn't need network. Cached layer; runs once per build.
#
# R14 BUG-DOCKER-PUPPETEER: PUPPETEER_CACHE_DIR was previously /app/.puppeteer-cache
# but the WORKDIR /app dir itself is owned by root (only the COPY'd contents
# get chowned to node via --chown=node:node). USER node then hit EACCES on
# mkdir of the cache dir and the build logged "Chrome install/smoke test
# failed — thumbnails will be unavailable" on every clean build. Move the
# cache to node's home (/home/node/.cache/puppeteer), which the node:22-slim
# base image already creates and owns. Runtime container inherits the same
# path because the api service also runs as USER node.
# Use the distro Chromium installed above, matching the runtime executable.
# Avoid a redundant network download of Puppeteer's fallback browser on each
# source update. Fail the build if the configured browser is not executable.
USER node
ENV PUPPETEER_CACHE_DIR=/home/node/.cache/puppeteer
# Point Puppeteer at the apt-installed `chromium` so puppeteer.launch()
# always uses a native-arch binary (see the chrome-arch comment above the
# apt install block). The bundled `puppeteer browsers install chrome`
# step below still runs as a fallback / cache-warmer — if PUPPETEER_EXECUTABLE_PATH
# is ever unset at runtime, the bundled binary takes over.
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
RUN mkdir -p "$PUPPETEER_CACHE_DIR" \
&& cd services/api && pnpm exec puppeteer browsers install chrome \
&& /usr/bin/chromium --headless=new --no-sandbox --disable-gpu --version \
|| (echo "[api-image] Chromium smoke test failed — thumbnails will be unavailable" && true)
&& /usr/bin/chromium --headless=new --no-sandbox --disable-gpu --version

EXPOSE 4000
ENTRYPOINT ["tmux-entrypoint"]
Expand All @@ -202,8 +195,8 @@ CMD ["ws", "npx", "tsx", "services/ws/src/index.ts"]
# --- Web (Next.js standalone) ---
FROM base AS web
WORKDIR /app
COPY --from=build --chown=node:node /app/apps/web/.next/standalone ./
COPY --from=build --chown=node:node /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=web-build --chown=node:node /app/apps/web/.next/standalone ./
COPY --from=web-build --chown=node:node /app/apps/web/.next/static ./apps/web/.next/static
# Runtime placeholder rewriter — replaces __DOABLE_*_URL__ placeholders
# baked at build time with the operator's actual NEXT_PUBLIC_* values from
# container env. Lets one image work for any deployment URL.
Expand Down
19 changes: 19 additions & 0 deletions deployment/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,22 @@ rm -f deployment/docker/.env
rm -rf deployment/docker/certs/*.pem
./deployment/docker/setup.sh
```

### Source-build resource usage

`WEB_BUILD_MEMORY_MB` is an optional Docker build argument that bounds both the
Node.js heap and the Next.js Turbopack memory cache for the web compilation:

```sh
docker compose --env-file deployment/docker/.env -f deployment/docker/docker-compose.yml build --build-arg WEB_BUILD_MEMORY_MB=512 web
```

The default is unset, preserving the existing compiler defaults. A small budget
can increase build time or be insufficient for a particular build; this is not a
container memory limit. Provision build capacity separately from running apps.

The API's npm download cache is kept in the `npm_cache` named volume so rebuilding
the API does not discard cached project dependencies. Project source remains in
`api_projects`. Caddy compresses supported preview/static asset responses; AI
SSE endpoints are outside the compression path matcher and `text/event-stream`
is excluded from its response MIME matcher.
3 changes: 3 additions & 0 deletions deployment/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ services:
volumes:
- api_projects:/app/services/api/projects
- api_thumbnails:/app/services/api/thumbnails
# Preserve dependency downloads across API updates; never store project source here.
- npm_cache:/home/node/.npm
# Published static sites — written here by the deploy adapters, served
# read-only by caddy from /srv/sites (path topology).
- doable_sites:/data/sites
Expand Down Expand Up @@ -378,6 +380,7 @@ volumes:
postgres_data:
api_projects:
api_thumbnails:
npm_cache:
ws_projects:
caddy_data:
caddy_config:
Expand Down