Commit Graph

12 Commits

Author SHA1 Message Date
ghost
f7c633ef20 fix(preview): bind :3000 instantly and bound diagnostics posts (#2286)
* fix(preview): bind :3000 instantly and bound diagnostics posts

The trusted preview deploy chain now succeeds end to end (after #2124,
#2207, #2217), but the preview container itself dies on Cloudflare with
"Container crashed while checking for ports" and no entrypoint
diagnostics ever arrive (run 27186150190).

Two compounding causes, both reproduced/measured locally against the
pinned @cloudflare/containers 0.3.3 behavior:

1. The port window is unwinnable. The library waits a hardcoded ~20s for
   the container port, but the entrypoint only binds :3000 after redis,
   postgres, and rails db:prepare complete -- measured at 69s under a
   basic instance's 1/4 vCPU. Fix: bind :3000 within ~1s via a tiny Ruby
   placeholder responder (static 503 + meta-refresh, input ignored),
   verified with a TCP connect poll, and released just before the real
   server starts. The worker still gates previewReady on the real Rails
   /up probe and sample data, so readiness semantics are unchanged.

2. Diagnostics could stall boot and never deliver. emit_status used curl
   with no timeout against the worker, whose Durable Object can be
   unresponsive while it waits for this container's port (observed as
   15s status timeouts in the failed run) -- so boot could deadlock
   against the port check, and failure detail (#2217) never reached the
   diagnostics artifact. Fix: --connect-timeout 2 --max-time 5 on all
   posts, progress events fire-and-forget in the background, and failure
   paths flush synchronously before exit.

Validated locally at Cloudflare basic limits (1 GiB / 0.25 vCPU): first
:3000 response in 1s, full boot to Rails /up=200 at ~76s with no OOM,
and a forced postgres failure exits 1 in 4s with the failure event
flushed. With the port window satisfied, the next real failure will
finally surface postgres detail in _container_status and CI artifacts.

* fix(preview): never read from placeholder clients

Superagent flagged on PR #2286 that the single-threaded :3000 placeholder
blocked on client.readpartial, so one connection that sends nothing -- such
as a bare TCP port probe, which is exactly what the Cloudflare port check
performs -- would wedge the accept loop and starve every later probe.

The response is static, so drop the read entirely: each connection is
written the 503 warming page and closed immediately, making the loop
effectively non-blocking per client.

Regression-tested by holding three idle TCP connections open while HTTP
probes still answered 503 immediately; full boot under basic-instance
limits (1 GiB / 0.25 vCPU) still reaches Rails /up=200 with the placeholder
answering at 1s.

* fix(preview): stop postgres crashing on Cloudflare's small /dev/shm

This is the actual root cause of the preview container dying on Cloudflare
with "Container crashed while checking for ports" (run 27341808838) and the
maintainer's "dies immediately after postgres-start" (#2217).

Reproduced locally: running the preview image with a tiny /dev/shm and a
WRITABLE root (the realistic Cloudflare model) crashes postgres on startup
with:

  FATAL: could not resize shared memory segment "/PostgreSQL..." to
         1048576 bytes: No space left on device

PostgreSQL 17 defaults to dynamic_shared_memory_type = posix, which
allocates dynamic shared memory in /dev/shm. Cloudflare Containers provide
only a tiny /dev/shm, so postgres FATALs before the entrypoint can bind a
port, and the container exits -> the supervisor reports it crashed during
the port check. Local Docker hid this because its default /dev/shm is 64MB.
Memory was ruled out (boots fine at -m 512m); it is specifically /dev/shm.

Fix: set dynamic_shared_memory_type = mmap so DSM is file-backed in the
data directory instead of /dev/shm. The build comments out the default
posix line, appends mmap, verifies the result, and fails hard if
postgresql.conf is missing so this critical setting cannot silently regress.

Also log fail_preview reasons to stderr so the real failure is captured by
Cloudflare container observability even when the HTTP diagnostics channel is
blocked by the worker's port wait.

Verified at Cloudflare basic-equivalent limits (1 GiB / 0.25 vCPU,
/dev/shm 64k): before, exit 1 right after "Starting PostgreSQL..."; after,
"PostgreSQL is ready" -> Rails /up=200 (~97s), placeholder answering :3000
within 1s. Normal-resource boot still reaches /up=200 in ~9s.

* fix(preview): use standard-1 instance and widen CI readiness poll

Two changes the preview needs to actually deploy and be reported ready on
Cloudflare, both validated by deploying to a real CF account.

- instance_type basic -> standard-1. The container runs postgres + redis +
  puma AND generates the full demo dataset (Demo::Generator, ~12 years of
  transactions), which peaks just over basic's 1 GiB and OOM-kills the
  container (exit 137) before demo-data-ready. standard-1 (1/2 vCPU, 4 GiB)
  completes it. Measured on real Cloudflare: rails ready ~46s, demo data
  ~149s, peak well under 4 GiB, no OOM.

- "Collect preview diagnostics" poll budget 40 -> 100 (~128s -> ~350s), with
  the matching guard in bin/preview_deploy_security_check.rb. A real CF
  standard-1 run reached previewReady at ~195s, so the old 40-poll budget
  would have failed a working preview before it finished warming up. The
  loop still breaks early on previewReady/previewFailed.

* fix(preview): apply DSM override to the cluster the entrypoint starts

The build selected postgresql.conf via `find ... | head -1`, which picks an
arbitrary cluster, while the entrypoint starts the highest-version cluster
(ls /etc/postgresql | sort -V | tail -1). Today only PG17 is installed so they
coincide, but if a second major version were ever present the override could
land on a cluster that never runs, silently reintroducing the /dev/shm crash.

Derive PG_CONF from the same highest-version cluster the entrypoint starts so
the dynamic_shared_memory_type=mmap override always applies to the active
cluster. Verified: build edits /etc/postgresql/17/main/postgresql.conf, and at
runtime under a tiny /dev/shm postgres starts with SHOW
dynamic_shared_memory_type = mmap.

* fix(preview): apply pg_hba trust rules to the cluster the entrypoint starts

Same latent issue as the dynamic_shared_memory_type override: the build wrote
the local `trust` rules to `find ... | head -1` (arbitrary cluster), while the
entrypoint starts the highest-version cluster. With a single PG17 they coincide,
but a second major version would send the trust rules to a cluster that never
runs, breaking the entrypoint's trust-auth db setup.

Derive PG_HBA from the same highest-version cluster (ls /etc/postgresql |
sort -V | tail -1) and fail the build if it's missing. Verified under a tiny
/dev/shm: postgres starts and CREATE ROLE/CREATE DATABASE succeed via trust.
2026-06-12 10:40:35 +02:00
Sure Admin (bot)
cf60e2c1d6 chore(ci): finish Node 24 GitHub Actions migration (#2221)
* chore(ci): finish Node 24 GitHub Actions migration

* chore(ci): update preview security check for github-script v8
2026-06-06 16:41:07 +02:00
ghost
85d7695d1f ci(preview): render Cloudflare config from trusted template (#2207) 2026-06-06 05:54:40 +02:00
ghost
92fa73ef00 ci(preview): fix Cloudflare registry image deployment (#2124)
* ci(preview): fix Cloudflare registry image deployment

Keep the preview workflow's secret-bearing deploy path on trusted tooling while
rewriting Wrangler config through registry-shaped image refs for push and deploy.
Centralize preview log redaction and extend resolver/security guard coverage for
artifact identity conflicts.

* ci(preview): keep failure diagnostics resilient

* ci(preview): redact private key diagnostics
2026-06-03 00:11:36 +02:00
ghost
1af880aa2a ci(preview): stabilize image push and readiness diagnostics (#2084)
* ci(preview): rewrite image config before registry push

Point the trusted preview deploy config at the loaded CI image before Wrangler validates the worker config for the Cloudflare registry push. This keeps the existing trusted deploy boundary intact while fixing the post-2062 image-push ordering regression.

* ci(preview): require trusted readiness diagnostics

* ci(preview): use nonce for diagnostics events

* ci(preview): retain diagnostics timing anchors
2026-06-01 10:51:29 +02:00
ghost
5f8452d63b ci(preview): stabilize Cloudflare preview deployments (#2062)
* ci(preview): stabilize Cloudflare preview deployments

* ci(preview): bound diagnostics and cover artifact fallback

* ci(preview): isolate artifact deploy permissions

* ci(preview): tidy deployment comment rendering

* ci(preview): harden preview manifest generation

* ci(preview): fail on preview diagnostics failure
2026-05-31 13:30:03 +02:00
ghost
e28b883107 ci(preview): split PR image builds from trusted deploys (#2057)
* ci(preview): split PR image builds from trusted deploys

* ci(preview): harden preview artifact handoff

Move the preview image artifact into the trusted preview workflow as a no-secret build job, gate deployment on base-trusted workflow definitions, and keep Cloudflare credentials isolated to the deploy-only job.

Also fail closed when the pushed image reference is not written into wrangler.toml and expand the preview deploy guard to enforce the same-run artifact and permission boundaries.

* ci(preview): move preview builds out of privileged trigger

* ci(preview): avoid secret-shaped wrangler env assignments

* ci(preview): keep wrangler credential env explicit
2026-05-30 15:45:43 +02:00
ghost
adabc55937 ci(preview): isolate preview deployment tooling (#2025)
* ci(preview): isolate deployment tooling

Keep PR preview source separate from the deployment toolchain by building a temporary deploy workspace from base-revision preview metadata and PR-owned source.

Add a focused CI guard so future preview workflow edits preserve the trusted tooling split.

* ci(preview): harden workflow guard checks

Address CodeRabbit feedback by making the preview deploy guard assertions collision-proof and more resilient to equivalent GitHub Actions expression and workspace path forms.

* ci(preview): normalize workflow guard paths

* ci(preview): defer workflow guard validation

* revert(preview): restore workflow guard validation

* ci(preview): gate preview deployments
2026-05-30 00:54:20 +02:00
Sure Admin (bot)
d8a12ad6be fix(preview): only redeploy on preview-cf label changes (#1980) 2026-05-25 15:31:00 +02:00
Sure Admin (bot)
cc2465b7a7 chore(ci): upgrade GitHub Actions to Node 24-compatible versions (#1810) 2026-05-17 11:06:18 +02:00
Sure Admin (bot)
d74b1b2a11 fix(preview): use worker list metadata for cleanup (#1799)
* fix(preview): use worker list metadata for cleanup

* fix(preview): handle cleanup edge cases

* fix(preview): harden scheduled cleanup errors

* feat(preview): add warmup screen and readiness gate

* fix(preview): report success after image deploy

* fix(preview): stop blocking healthy previews on stale status
2026-05-16 15:26:30 +02:00
Juan José Mata
6a765a90c6 chore: GitHub workflow to auto-deploy PRs to Cloudflare (#880)
* feat: add Cloudflare Containers PR preview deployments

Add GitHub workflows to automatically deploy PRs to Cloudflare
Containers after tests pass, with automatic cleanup after 24 hours.

Components:
- workers/preview/: Cloudflare Worker entry point that routes
  traffic to the Rails container
- preview-deploy.yml: Deploys PRs after CI passes, comments
  preview URL on PR
- preview-cleanup.yml: Cleans up previews on PR close or after
  24 hours via scheduled job

The container sleeps after 30 minutes of inactivity and wakes
automatically on the next request.

Required secrets:
- CLOUDFLARE_API_TOKEN
- CLOUDFLARE_ACCOUNT_ID
- CLOUDFLARE_WORKERS_SUBDOMAIN

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: use development environment with embedded PostgreSQL for previews

- Add preview-specific Dockerfile with PostgreSQL server included
- Add docker-entrypoint.sh to start PostgreSQL and run migrations
- Change RAILS_ENV from production to development
- Auto-generate SECRET_KEY_BASE and DATABASE_URL for self-contained previews

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* feat: add Redis to preview container

- Install redis-server in the preview Dockerfile
- Start Redis in the entrypoint before PostgreSQL
- Auto-configure REDIS_URL for Sidekiq background jobs

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: mark GitHub deployment inactive on manual PR cleanup

When using workflow_dispatch with a specific pr_number, the workflow
now also marks the associated GitHub deployment as inactive, mirroring
the behavior of the batch cleanup path.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: remove npm cache config that requires missing lockfile

The setup-node action's cache feature requires a package-lock.json
which doesn't exist in workers/preview/. Remove the cache configuration
to fix the workflow.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: only update deployment status when deployment ID exists

Add condition to check steps.deployment.outputs.result exists before
attempting to update deployment status. This prevents a JavaScript
syntax error when the deployment step fails and no ID is available.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: quote shell variables to fix SC2086 shellcheck warning

Quote the --var argument and GITHUB_OUTPUT redirection to prevent
word splitting issues.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: add permissions for deployment status operations

Add deployments: write permission to the cleanup workflow so the
GITHUB_TOKEN can list and update deployment statuses.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: specify build context for Dockerfile in wrangler config

Use object syntax for image config to set build context to repository
root, allowing the Dockerfile to reference files from both the root
(Gemfile, .ruby-version) and workers/preview/ (docker-entrypoint.sh).

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: run wrangler from repo root for correct build context

- Update workflow to run wrangler with --config flag from repo root
- Update wrangler.toml paths (main, image) to be relative to repo root
- Embed entrypoint script directly in Dockerfile using heredoc
- Remove separate docker-entrypoint.sh file

This ensures the Docker build context includes Gemfile, .ruby-version,
and other files at the repo root.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: move preview Dockerfile to repo root for correct build context

Wrangler resolves paths relative to the config file, not the current
directory. Moving Dockerfile.preview to repo root ensures:
- Build context is the repo root (where Gemfile, .ruby-version are)
- Path in wrangler.toml is ../../Dockerfile.preview (relative to config)
- Worker runs from workers/preview/ directory again

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: use find to locate pg_hba.conf instead of glob in redirection

Shell glob patterns don't work with redirection operators. Use find
to locate the actual pg_hba.conf path before writing to it.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix: enable workers_dev for preview deployments

Add workers_dev = true to make the preview worker accessible via
the workers.dev subdomain.

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* feat: enable observability for container logs

https://claude.ai/code/session_013EZuzBxWPEEYp3TQptXWdP

* fix preview container boot path

* fix: set preview container startup command explicitly

* fix: update preview worker compatibility date

* chore: expose preview container diagnostics

* fix: recover from stale preview container state

* fix: harden preview container startup paths

* chore: report preview startup stages

* fix: bypass stale container helper state during recovery

* fix: allow longer preview container startup

* fix: upgrade preview container runtime

* fix: use supported node version for preview deploy

* fix: use public container startup flow

* fix: simplify preview container startup

* chore: retain preview container diagnostic history

* fix: bypass systemctl redirect for postgres startup

* chore: probe rails readiness from inside preview container

* chore: capture rails process and port diagnostics

* chore: capture rails startup logs on preview timeout

* fix: align preview bind behavior with ipv6 startup model

* chore: capture preview socket state on rails timeout

* chore: capture rails wait state and child processes

* fix: launch preview with puma directly

* fix: run preview in production mode

* chore: probe preview app boot before puma

* fix: disable lookbook routes in production preview

* chore: capture ruby backtrace from hung boot probe

* fix: disable bootsnap in preview runtime

* fix: disable sidekiq web routes in production preview

* chore: trace hung preview boot probe with strace

* fix: json-escape preview telemetry payloads

* fix: pass preview telemetry env vars correctly

* chore: signal ruby child for preview boot backtrace

* fix: allow longer preview cold-start budget

* fix: skip sidekiq web requires in production preview

* chore: deploy hello world preview container

* fix(preview): restore rails image without redundant warmup

* feat(preview): seed demo dataset on boot

* ci(preview): require preview-cf label

* ci(preview): reuse pr workflow checks

* fix(preview): avoid clearing demo data in production boot

* fix(preview): tolerate already-running postgres on boot

* fix(preview): check demo user via psql during boot

* fix(preview): defer heavy demo seed until after boot

* fix(preview): move demo-user creation after rails boot

* fix(preview): fail fast on container lifecycle errors

* fix(preview): validate manual cleanup pr input

* fix(preview): parameterize preview pr number

* ci(preview): use setup-node v6

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: KiloClaw <kiloclaw@openclaw.ai>
2026-05-15 23:14:20 +02:00