Skip to content

TIM-46: drop Cloudflare, run as Node service in Docker Compose#72

Open
timoa wants to merge 1 commit into
mainfrom
agent/frontend-sveltekit/d34acaa8
Open

TIM-46: drop Cloudflare, run as Node service in Docker Compose#72
timoa wants to merge 1 commit into
mainfrom
agent/frontend-sveltekit/d34acaa8

Conversation

@timoa

@timoa timoa commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Stage 1b — Node runtime + Docker Compose

Resolves TIM-46 (parallel sub-issue of TIM-45).

Replaces @sveltejs/adapter-cloudflare with @sveltejs/adapter-node so the same binary serves both docker compose (this PR) and the future Helm chart (Stage 3).

What changed

  • adapter: @sveltejs/adapter-cloudflare@sveltejs/adapter-node (out: 'build', precompress: false). PORT/HOST env vars are honoured by the adapter.
  • scripts: dropped pnpm deploy (Cloudflare Pages deploy); added pnpm start (node build/index.js).
  • deps: removed wrangler (now unused).
  • files: deleted wrangler.toml.
  • Dockerfile: multi-stage on node:24-alpine, corepack-pinned pnpm@10.30.3, BuildKit cache mount for /pnpm/store, pnpm prune --prod after build, non-root node user, HEALTHCHECK hitting /healthz via wget.
  • .dockerignore: trims node_modules, .svelte-kit, build artefacts, env files, secrets, tests, docs, source control.
  • docker-compose.yml: app only — env_file, HOST/PORT overrides, restart: unless-stopped, published port.
  • docker-compose.postgres.yml: Compose v2.20+ include: overlay that adds a bundled postgres:16-alpine with pg_isready healthcheck and a named pgdata volume. Also brings in depends_on: postgres healthy on the app service so it waits for Postgres to be ready.
  • /healthz: unauthenticated, no-DB liveness/readiness endpoint returning { ok: true } — Kubernetes/Compose probe-friendly.
  • .env.example: rewritten to list every env var the app reads (DATABASE_URL, BETTER_AUTH_*, GITHUB_*, PUBLIC_APP_URL, MISTRAL_API_KEY, HOST/PORT) with two commented DATABASE_URL forms for bundled vs external Postgres. Supabase vars kept and marked DEPRECATED (Stage 4 will remove them).

Verification

  • pnpm check: 0 errors (1 pre-existing svelte warning unrelated).
  • pnpm lint: clean.
  • pnpm test: 148/148 pass.
  • pnpm build: produces a Node-runnable bundle in build/.
  • docker build .: succeeds.
  • docker compose -f docker-compose.yml -f docker-compose.postgres.yml up: both services healthy; curl http://localhost:3000/healthz200 with {"ok":true}.

Usage

# Bundled Postgres (dev default)
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up

# External Postgres (managed / K8s / shared cluster — point DATABASE_URL in .env at it)
docker compose -f docker-compose.yml up

Out of scope (handled in later stages)

  • README, AGENTS.md, CONTRIBUTING.md, .cursor/rules rewrite → Stage 4 (TIM-50).
  • .github/workflows/deploy.yml (Cloudflare deploy workflow) → Stage 4 (TIM-50).
  • Helm chart → Stage 3 (TIM-52).
  • Supabase wiring removal → Stage 4 (TIM-50). Untouched here per Stage 1b non-goals.

Ready for review by @staff-engineer.

Summary by CodeRabbit

  • New Features

    • Added Docker-based run options, including a bundled PostgreSQL setup and an external database mode.
    • Added a built-in health check endpoint for container monitoring.
    • Expanded environment setup guidance with clearer configuration options.
  • Bug Fixes

    • Improved container startup reliability with a production-ready Node deployment setup.
    • Added health checks and safer runtime defaults for smoother deployments.
  • Chores

    • Updated project configuration to match the new deployment and container workflow.

Stage 1b of the full-Docker rewrite (TIM-45 / TIM-46). Replaces
@sveltejs/adapter-cloudflare with @sveltejs/adapter-node so the same
binary serves both docker compose and the future Helm chart.

- adapter: swap @sveltejs/adapter-cloudflare -> @sveltejs/adapter-node
  (out: 'build', precompress: false).
- build: 'node build/index.js' is now the entry; PORT/HOST env vars are
  honoured by the adapter.
- scripts: drop the Cloudflare 'pnpm deploy' script; add 'pnpm start'.
- deps: remove wrangler (now unused).
- files: delete wrangler.toml.
- Dockerfile: multi-stage on node:24-alpine, corepack-pinned pnpm@10.30.3,
  /pnpm/store BuildKit cache mount, 'pnpm prune --prod' after build,
  non-root 'node' user, HEALTHCHECK hitting /healthz via wget.
- .dockerignore: trim node_modules, .svelte-kit, build artefacts,
  env files, secrets, tests, docs, source control.
- docker-compose.yml: app only (env_file, HOST/PORT overrides, restart,
  published port).
- docker-compose.postgres.yml: Docker Compose v2.20+ 'include:' overlay
  that adds a bundled postgres:16-alpine with pg_isready healthcheck and
  a named 'pgdata' volume. Brings in 'depends_on: postgres healthy' on
  the app service.
- /healthz: unauthenticated, no-DB liveness/readiness endpoint returning
  { ok: true } (Kubernetes/Compose probe-friendly).
- .env.example: rewrite to list every env var the app reads
  (DATABASE_URL, BETTER_AUTH_*, GITHUB_*, PUBLIC_APP_URL, MISTRAL_API_KEY,
  HOST/PORT) with two commented DATABASE_URL forms for bundled vs
  external Postgres; Supabase vars kept and marked DEPRECATED (Stage 4
  will remove them).

Verification:
- pnpm check: 0 errors (1 pre-existing svelte warning unrelated).
- pnpm lint: clean.
- pnpm test: 148/148 pass.
- pnpm build: produces a Node-runnable bundle in build/.
- docker build .: succeeds.
- docker compose -f docker-compose.yml -f docker-compose.postgres.yml up:
  both services healthy, curl http://localhost:3000/healthz -> 200 with
  {"ok":true}.

Non-goals respected: Supabase wiring (locals.supabase.*) untouched.
README, AGENTS.md, CONTRIBUTING.md, .cursor/rules and the deploy GH
workflow will be reworked in Stage 4.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Migrates the SvelteKit app from Cloudflare Pages (@sveltejs/adapter-cloudflare, wrangler) to a Node adapter (@sveltejs/adapter-node). Adds a multi-stage Dockerfile, a base docker-compose.yml, a bundled-Postgres overlay (docker-compose.postgres.yml), a /healthz liveness endpoint, a .dockerignore, and an expanded .env.example.

Changes

Node Adapter and Docker Deployment

Layer / File(s) Summary
Node adapter and start script
svelte.config.js, package.json
Replaces @sveltejs/adapter-cloudflare with @sveltejs/adapter-node (output to build, precompression disabled), removes platformProxy config, swaps the deploy script for start (node build/index.js), and removes wrangler from devDependencies. Also deletes wrangler.toml.
Healthcheck endpoint
src/routes/healthz/+server.ts
Adds a GET handler returning json({ ok: true }) as a dependency-free liveness/readiness probe.
Multi-stage Dockerfile
Dockerfile
Three-stage build: deps installs pnpm dependencies with frozen lockfile and cache mount; build sets placeholder PUBLIC_* env vars, runs pnpm build, and prunes to production deps; runtime copies artifacts with node ownership, runs as non-root, exposes port 3000, and defines a wget-based HEALTHCHECK against /healthz.
Docker Compose files
docker-compose.yml, docker-compose.postgres.yml
docker-compose.yml defines the app service with env-file loading and port mapping. docker-compose.postgres.yml is an overlay that adds a postgres:16-alpine service with pg_isready healthcheck, named volume, and makes app depend on postgres being healthy.
Environment example and .dockerignore
.env.example, .dockerignore
.env.example gains documented sections for Postgres connection, Better Auth, GitHub OAuth, GitHub App, app settings, Mistral AI, and deprecated Supabase variables. .dockerignore excludes build artifacts, secrets, editor noise, OS junk, source control metadata, and test fixtures from the Docker build context.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

enhancement

Poem

🐇 Hop hop, away from Cloudflare's gate,
A Node adapter seals our fate!
Dockerfile built in stages three,
Postgres healthchecked, happy and free.
.dockerignore sweeps the mess away —
The rabbit ships to prod today! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative, but it does not follow the required template sections like Type of change, Checklist, or Screenshots. Add the template headings and fill in the Type of change, checklist items, testing/build confirmations, and screenshots section if applicable.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: moving from Cloudflare to a Node service for Docker Compose.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/frontend-sveltekit/d34acaa8

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.89%. Comparing base (df58ca9) to head (16c9412).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #72   +/-   ##
=======================================
  Coverage   88.89%   88.89%           
=======================================
  Files           6        6           
  Lines        1369     1369           
  Branches      309      309           
=======================================
  Hits         1217     1217           
  Misses        152      152           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
.env.example (1)

66-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add trailing newline at end of file.

The file is missing a final newline. This is a minor formatting fix to satisfy POSIX text file conventions and quiet dotenv-linter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example around lines 66 - 74, The .env.example file is missing a final
trailing newline, so update the file ending to include one. Keep the existing
Supabase env entries unchanged and ensure the file terminates cleanly to satisfy
dotenv-linter and POSIX text conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Around line 1-74: Add the missing AI_OPTIMIZATION_MODEL placeholder to
.env.example so it matches src/lib/server/config/app-config.ts and the README.
Place it near the other app/server configuration variables, using a clear
commented example value so local setup users know to set the override
consistently.

In `@docker-compose.yml`:
- Around line 29-36: The container’s internal listen port is being changed by
the PORT environment override while the service mapping and health probe still
assume port 3000, so make the app listen on a fixed container port unless the
whole stack is updated together. In docker-compose.yml, keep PORT aligned with
the published container port or stop using it to control the in-container bind
port, and ensure the Dockerfile health check/probe for the app matches the same
fixed port used by the service.

In `@Dockerfile`:
- Around line 32-38: Remove the placeholder PUBLIC_* ENV block from the
Dockerfile and stop relying on build-time inlined public env values; the issue
is that these values are baked into the SvelteKit bundle via $env/static/public,
so runtime overrides cannot replace them. Update the Supabase/public env usage
in the relevant app code to use $env/dynamic/public where appropriate, or
otherwise ensure the build does not require placeholder values in the image. Use
the Dockerfile public env section and the SvelteKit env imports as the key
places to adjust.

In `@svelte.config.js`:
- Around line 1-8: The Svelte config has been switched from the Cloudflare Pages
adapter to the Node adapter, which changes the deployment model. Revert the
adapter change in config and keep using the existing Cloudflare Pages setup by
preserving the adapter configuration in svelte.config.js, ensuring the
deployment contract remains unchanged.

---

Nitpick comments:
In @.env.example:
- Around line 66-74: The .env.example file is missing a final trailing newline,
so update the file ending to include one. Keep the existing Supabase env entries
unchanged and ensure the file terminates cleanly to satisfy dotenv-linter and
POSIX text conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53fb3b4a-7c1f-4e4a-a1dd-e9643443721e

📥 Commits

Reviewing files that changed from the base of the PR and between a44db43 and 16c9412.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • .dockerignore
  • .env.example
  • Dockerfile
  • docker-compose.postgres.yml
  • docker-compose.yml
  • package.json
  • src/routes/healthz/+server.ts
  • svelte.config.js
  • wrangler.toml
💤 Files with no reviewable changes (1)
  • wrangler.toml

Comment thread .env.example
Comment on lines +1 to +74
# =============================================================================
# DATABASE — pick ONE of the two blocks below and delete the other.
# =============================================================================

# --- (A) Bundled Postgres (docker compose with the postgres overlay) -------
# Use this when `docker compose -f docker-compose.yml -f docker-compose.postgres.yml up`.
# The hostname `postgres` is the service name in docker-compose.postgres.yml.
# POSTGRES_PASSWORD is also defined below for the postgres container itself.
DATABASE_URL=postgres://workflow_metrics:${POSTGRES_PASSWORD}@postgres:5432/workflow_metrics

# --- (B) External Postgres (managed, K8s, or your own instance) -----------
# Uncomment and replace the placeholders. sslmode=require is recommended for
# managed Postgres providers (Neon, RDS, Crunchy Bridge, Supabase pooled, …).
# DATABASE_URL=postgres://workflow_metrics:CHANGE_ME@db.example.com:5432/workflow_metrics?sslmode=require

# Required only when using bundled mode (block A). The compose overlay reads
# this to provision the postgres container.
POSTGRES_PASSWORD=change-me-local-dev-password
# Optional — defaults are workflow_metrics / workflow_metrics if unset.
# POSTGRES_DB=workflow_metrics
# POSTGRES_USER=workflow_metrics

# =============================================================================
# BETTER AUTH — Stage 2a wires these. Listed here so docker compose / Helm
# can inject them now without a second round of edits.
# Generate BETTER_AUTH_SECRET with: openssl rand -base64 32
# =============================================================================
BETTER_AUTH_SECRET=change-me-run-openssl-rand-base64-32
# The public URL where the app is reachable. Used as the OAuth callback base.
BETTER_AUTH_URL=http://localhost:3000

# =============================================================================
# GITHUB OAUTH (login)
# Create an OAuth App at https://github.com/settings/applications/new.
# Authorization callback URL (after Better Auth lands) = ${BETTER_AUTH_URL}/api/auth/callback/github
# =============================================================================
GITHUB_CLIENT_ID=your-github-oauth-app-client-id
GITHUB_CLIENT_SECRET=your-github-oauth-app-client-secret

# =============================================================================
# GITHUB APP (Apply as PR)
# Separate from the OAuth App above. Create at https://github.com/settings/apps/new.
# Repository permissions: Contents (R/W), Pull requests (R/W), Workflows (R/W), Actions (R).
# =============================================================================
GITHUB_APP_ID=your-github-app-id
# Multi-line PEM is fine. Or collapse newlines to literal \n for single-line secrets.
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
GITHUB_APP_SLUG=your-github-app-slug

# =============================================================================
# APP
# =============================================================================
# Production URL of the app (no trailing slash). OAuth redirects back here.
PUBLIC_APP_URL=http://localhost:3000
# Node adapter binds to HOST:PORT (defaults 0.0.0.0:3000). Override per-env here.
HOST=0.0.0.0
PORT=3000

# =============================================================================
# MISTRAL AI (optional)
# Per-user keys are entered in Settings and stored in the DB. This server-wide
# fallback is used when no per-user key is set (e.g. demos).
# =============================================================================
# MISTRAL_API_KEY=your-mistral-api-key

# =============================================================================
# DEPRECATED — Supabase
# The app still calls `locals.supabase.*`. These are required until Stage 4
# removes the Supabase wiring. Do not delete yet.
# =============================================================================
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# Server-only; bypasses RLS for cache writes. Never expose to the browser.
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find all env variable accesses in server code and compare with .env.example

# Extract env keys from .env.example (non-comment, non-empty lines)
grep -E '^[A-Z_]+=' .env.example | sed 's/=.*//' | sort -u > /tmp/env_example_keys.txt

# Find all env.* and env['.*'] accesses in server code
rg -nP 'env\.[A-Z_]+|env\[[\'"][A-Z_]+[\'"]\]' src/lib/server src/routes --type=ts | \
  grep -oP 'env\.\K[A-Z_]+|env\[\K[\'"]\K[A-Z_]+' | sort -u > /tmp/env_code_keys.txt

echo "=== In code but not in .env.example ==="
comm -23 /tmp/env_code_keys.txt /tmp/env_example_keys.txt

echo "=== In .env.example but maybe not in code ==="
comm -13 /tmp/env_code_keys.txt /tmp/env_example_keys.txt

Repository: timoa/workflow-metrics

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Files =="
git ls-files .env.example README* src | sed -n '1,200p'

echo
echo "== .env.example keys =="
grep -E '^[A-Z_][A-Z0-9_]*=' .env.example | sed 's/=.*//' | sort -u

echo
echo "== Server env usages (src/lib/server, src/routes) =="
python3 - <<'PY'
import re, pathlib
roots = [pathlib.Path('src/lib/server'), pathlib.Path('src/routes')]
pat = re.compile(r'\benv\.([A-Z_][A-Z0-9_]*)|\benv\[[\'"]([A-Z_][A-Z0-9_]*)[\'"]\]')
keys = set()
for root in roots:
    if not root.exists():
        continue
    for path in root.rglob('*.ts'):
        txt = path.read_text(encoding='utf-8', errors='ignore')
        for m in pat.finditer(txt):
            keys.add(m.group(1) or m.group(2))
for k in sorted(keys):
    print(k)
PY

echo
echo "== README env mentions (best effort) =="
rg -n --hidden -S 'BETTER_AUTH_SECRET|BETTER_AUTH_URL|GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET|GITHUB_APP_ID|GITHUB_APP_PRIVATE_KEY|GITHUB_APP_SLUG|PUBLIC_APP_URL|HOST|PORT|MISTRAL_API_KEY|DATABASE_URL|POSTGRES_PASSWORD|PUBLIC_SUPABASE_URL|PUBLIC_SUPABASE_ANON_KEY|SUPABASE_SERVICE_ROLE_KEY' README* .github docs 2>/dev/null || true

Repository: timoa/workflow-metrics

Length of output: 8278


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Call sites =="
rg -n -C 3 '\b(AI_OPTIMIZATION_MODEL|CACHE)\b' src/lib/server src/routes README.md .github/workflows || true

echo
echo "== README env table area =="
sed -n '150,250p' README.md

Repository: timoa/workflow-metrics

Length of output: 6014


Add AI_OPTIMIZATION_MODEL to .env.example
src/lib/server/config/app-config.ts reads this override, and the README already documents it, but the example file does not. Add a placeholder so local setup stays in sync.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 56-56: [UnorderedKey] The HOST key should go before the PUBLIC_APP_URL key

(UnorderedKey)


[warning] 57-57: [UnorderedKey] The PORT key should go before the PUBLIC_APP_URL key

(UnorderedKey)


[warning] 72-72: [UnorderedKey] The PUBLIC_SUPABASE_ANON_KEY key should go before the PUBLIC_SUPABASE_URL key

(UnorderedKey)


[warning] 74-74: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example around lines 1 - 74, Add the missing AI_OPTIMIZATION_MODEL
placeholder to .env.example so it matches src/lib/server/config/app-config.ts
and the README. Place it near the other app/server configuration variables,
using a clear commented example value so local setup users know to set the
override consistently.

Source: Coding guidelines

Comment thread docker-compose.yml
Comment on lines +29 to +36
environment:
# Ensure the runtime picks up the right interface and port even if the
# operator forgets to export them in .env.
HOST: ${HOST:-0.0.0.0}
PORT: ${PORT:-3000}
NODE_ENV: production
ports:
- "${PORT:-3000}:3000"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the container's listen port fixed unless the rest of the stack follows it.

Line 32-L33 can move the app to a different internal port, but Line 36 still publishes container port 3000, and Dockerfile Line 72 probes 3000. Any PORT override makes the service unreachable and unhealthy.

Suggested fix
     environment:
       # Ensure the runtime picks up the right interface and port even if the
       # operator forgets to export them in .env.
       HOST: ${HOST:-0.0.0.0}
-      PORT: ${PORT:-3000}
+      PORT: 3000
       NODE_ENV: production
     ports:
       - "${PORT:-3000}:3000"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
environment:
# Ensure the runtime picks up the right interface and port even if the
# operator forgets to export them in .env.
HOST: ${HOST:-0.0.0.0}
PORT: ${PORT:-3000}
NODE_ENV: production
ports:
- "${PORT:-3000}:3000"
environment:
# Ensure the runtime picks up the right interface and port even if the
# operator forgets to export them in .env.
HOST: ${HOST:-0.0.0.0}
PORT: 3000
NODE_ENV: production
ports:
- "${PORT:-3000}:3000"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 29 - 36, The container’s internal listen
port is being changed by the PORT environment override while the service mapping
and health probe still assume port 3000, so make the app listen on a fixed
container port unless the whole stack is updated together. In
docker-compose.yml, keep PORT aligned with the published container port or stop
using it to control the in-container bind port, and ensure the Dockerfile health
check/probe for the app matches the same fixed port used by the service.

Comment thread Dockerfile
Comment on lines +32 to +38
# SvelteKit's `$env/static/public` requires PUBLIC_* vars at build time.
# The Supabase wiring still imports them (Stage 4 will remove it). Provide
# placeholders so `pnpm build` succeeds without a `.env` in the image. Real
# values come from runtime env_file in docker-compose.yml / the Helm chart.
ENV PUBLIC_SUPABASE_URL=https://placeholder.supabase.co \
PUBLIC_SUPABASE_ANON_KEY=placeholder-anon-key \
PUBLIC_APP_URL=http://localhost:3000

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

In SvelteKit 2.x, are $env/static/publicvalues substituted at build time, and can runtime environment variables override them in a built@sveltejs/adapter-node app?

💡 Result:

In SvelteKit 2.x, values from $env/static/public are substituted into your code at build time [1]. Because they are statically replaced with their values during the build process, they cannot be overridden by runtime environment variables in a built application [1][2]. If you need to access environment variables that can be changed at runtime, you should use $env/dynamic/public instead [2][3]. Unlike the static variant, $env/dynamic/public reads values from the environment at runtime (e.g., from process.env in a Node.js environment) [3]. Summary of differences: - $env/static/public: Values are baked into the bundle at build time, allowing for build-time optimizations (like dead code elimination). They are fixed once the build is complete [1][2]. - $env/dynamic/public: Values are accessed at runtime. This is the correct choice if you need to configure the application using environment variables after it has been built, such as when deploying the same build artifact to different environments [2][3].

Citations:


Avoid baking placeholder PUBLIC_* values into the image. Dockerfile:32-38 will inline these $env/static/public values at build time, so the placeholders ship in the bundle and runtime env_file/Helm env vars cannot override them. Switch this code to $env/dynamic/public or keep real build-time values out of the image.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 32 - 38, Remove the placeholder PUBLIC_* ENV block
from the Dockerfile and stop relying on build-time inlined public env values;
the issue is that these values are baked into the SvelteKit bundle via
$env/static/public, so runtime overrides cannot replace them. Update the
Supabase/public env usage in the relevant app code to use $env/dynamic/public
where appropriate, or otherwise ensure the build does not require placeholder
values in the image. Use the Dockerfile public env section and the SvelteKit env
imports as the key places to adjust.

Comment thread svelte.config.js
Comment on lines +1 to +8
import adapter from '@sveltejs/adapter-node';

/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
platformProxy: {
persist: { path: '.wrangler/state/v3' }
}
out: 'build',
precompress: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Don't switch this repo off the Cloudflare adapter.

This changes the deployment artifact from Cloudflare Pages to a long-running Node server, which breaks the repo's current deployment contract.

As per coding guidelines, svelte.config.js: "Use Cloudflare Pages for deployment with @sveltejs/adapter-cloudflare" and "Do not switch adapters."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@svelte.config.js` around lines 1 - 8, The Svelte config has been switched
from the Cloudflare Pages adapter to the Node adapter, which changes the
deployment model. Revert the adapter change in config and keep using the
existing Cloudflare Pages setup by preserving the adapter configuration in
svelte.config.js, ensuring the deployment contract remains unchanged.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant