Skip to content

build(serverpod)!: upgrade to Serverpod 4.0.4 for Cloud deployment - #114

Merged
moises-cisneros merged 12 commits into
mainfrom
chore/110-serverpod-4-upgrade
Oct 7, 2026
Merged

moises-cisneros merged 12 commits into
mainfrom
chore/110-serverpod-4-upgrade

Conversation

@TOMOKI977

Copy link
Copy Markdown
Contributor

Closes #110

Summary

Upgrades puls3 from Serverpod 3.4.13 to Serverpod 4.0.4, the version Serverpod Cloud requires (unblocks #31).

  • Toolchain pinned to Flutter 3.44.4 / Dart 3.12.2 / serverpod_cli 4.0.4 in the pubspecs, the lockfile, CI and the docs. Every serverpod* package resolves to 4.0.4.
  • Generated server, client and test-tools code was regenerated and committed together. Handwritten code was adapted to the 4.x APIs only where compilation required it.
  • The forced upgrade-4-0 migration is checked in. Serverpod 4 recreates serverpod_auth_idp_rate_limited_request_attempt to rename nonce to key. The generated SQL would drop the existing rows, so the migration was hand-edited to copy them through a transaction-scoped temp table. Rate-limit history is preserved, not reset (see Notes).
  • The Dockerfile uses dart build cli and ships the emitted bundle, because Serverpod 4 dependencies use Dart build hooks that dart compile exe cannot package.
  • A new root .dockerignore keeps .env files and config/passwords.yaml out of the build context. Before this change, a local passwords.yaml was baked into the runtime image; main has the same problem.
  • CI gets a docker job that builds the image and asserts the bundle is present and passwords.yaml is excluded. It runs on server, domain, lockfile and .dockerignore changes and is wired into gate.
  • docs/operations/serverpod-4-migration.md gives teammates the exact upgrade commands, explains the migration, and covers rollback.

Acceptance criteria

  • flutter --version reports 3.44.4 with Dart 3.12.2, and serverpod version reports 4.0.4.
  • All direct serverpod* dependencies and the lockfile resolve to 4.0.4; no 3.4.x package is left.
  • serverpod generate is clean, and generated server, client and integration-test helpers are committed together.
  • The upgrade-4-0 migration was generated and reviewed, and it applies cleanly to a disposable database and to a fresh Serverpod Cloud database. The rate-limit table behavior is documented; the rows are preserved instead of reset.
  • Handwritten endpoints, auth setup, web routes, ORM calls and session lifecycle compile, and the existing tests pass.
  • CI uses Flutter 3.44.4 and serverpod_cli 4.0.4.
  • The Docker image builds with dart build cli and contains the runnable bundle. The same server answers /health/check on Serverpod Cloud.
  • Server, domain and Flutter tests, static analysis and the Flutter web build pass.
  • A local smoke test against a disposable database started the server without target-state warnings.
  • The upgrade/rollback note lets a teammate reproduce the environment.
  • chore: deploy server to Serverpod Cloud and Flutter web app #31 is unblocked: the server is deployed on Serverpod Cloud from the branch stacked on this one.

Verification evidence

$ cd puls3_server && dart analyze --fatal-infos
No issues found!
$ dart test test/unit
00:40 +285: All tests passed!

# Earlier run on this branch: server 285, domain 105, Flutter 75 tests passed;
# flutter build web succeeded; serverpod generate left no diff;
# migration applied to a disposable database and the server started cleanly.

$ docker build --file puls3_server/Dockerfile --tag puls3-server:ci .   # exit 0
$ docker run --rm --entrypoint sh puls3-server:ci -c \
    'test -x server/bin/main && test -f config/production.yaml && test ! -e config/passwords.yaml' && echo CHECKS_OK
CHECKS_OK      # a local passwords.yaml existed on disk during this build

$ actionlint
.github/workflows/ci.yml:31:9: shellcheck SC2129 (style) — existing $GITHUB_OUTPUT pattern, unchanged

# Serverpod Cloud (deployed from #31's branch, which is stacked on this one)
$ curl -s -X POST -d '{}' https://puls3-hub-on-stellar.api.serverpod.space/health/check
{"__className__":"BackendHealth","version":"1.0.0+b88cdaf"}
$ scloud log | grep -i migration
INFO | applyMigrations: true
INFO | Applied database migration:

Notes for reviewers

  • Decided differently from the issue: the issue expected the auth rate-limit counters to reset. The migration keeps them by copying the rows (with nonce renamed to key) inside the same transaction. migration.sql:101-126 is the only hand-edited part of the generated migration.
  • A bounded 4-lens review (risk, reliability, resilience, readability) found no blockers. Its one security finding, the secrets in the Docker image, is fixed here. Remaining notes, not blocking: the runtime stage still uses alpine:latest, there is no automated test that runs the migration over pre-existing rate-limit rows, and the shell-form ENTRYPOINT comes from the Serverpod template.
  • Every teammate must upgrade their toolchain after merge. Follow docs/operations/serverpod-4-migration.md. A Serverpod 3 server must not run against a database that has the 4.0 migration applied.
  • 1fb9790 is marked ! because generated client APIs changed with the Serverpod 4 regeneration.

Copy serverpod_auth_idp_rate_limited_request_attempt rows through a
transaction-scoped temp table while the generator recreates the table to
rename nonce to key. Document the upgrade, migration and rollback steps.

Refs #110
…f the image

Serverpod 4 dependencies use Dart build hooks, so the image now uses
dart build cli and ships the emitted bundle. The new .dockerignore keeps
.env files and config/passwords.yaml out of the build context, which
previously baked local secrets into the runtime image.

Refs #110
Add a docker job, triggered by server, domain, lockfile and
.dockerignore changes, that builds the image with planted secret files
and asserts the bundle exists and passwords.yaml is excluded.

Refs #110
@TOMOKI977 TOMOKI977 added area: infra CI/CD, deploy, testnet, secrets type: chore Maintenance and tooling P0 Blocks a deadline deliverable labels Oct 7, 2026
@TOMOKI977 TOMOKI977 self-assigned this Oct 7, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Deploying puls3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 93c47ab
Status: ✅  Deploy successful!
Preview URL: https://c27b1e6b.puls3-4lw.pages.dev
Branch Preview URL: https://chore-110-serverpod-4-upgrad.puls3-4lw.pages.dev

View logs

…itory

The Pages build command cloned Flutter 3.41.4 from the dashboard, so every
branch on Serverpod 4 (Dart ^3.12.2) failed version solving. The build now
runs scripts/cloudflare-pages-build.sh, which installs the branch's pinned
Flutter. A test keeps its version in sync with CI.

Refs #110
@TOMOKI977

Copy link
Copy Markdown
Contributor Author

Cloudflare Pages fix (522f6d2): the Pages build command was configured in the dashboard to clone Flutter 3.41.4, so this branch failed version solving (Because _ requires SDK version ^3.12.2).

  • scripts/cloudflare-pages-build.sh now installs the branch's pinned Flutter (3.44.4) and builds the web app. scripts/tests/cloudflare-pages-build.test.sh keeps its version in sync with CI.
  • The dashboard build command was changed to a backward-compatible form, so main keeps deploying until this merges:
    if [ -f scripts/cloudflare-pages-build.sh ]; then bash scripts/cloudflare-pages-build.sh; else <previous 3.41.4 command>; fi
  • A retried preview build of 522f6d2 succeeded with Flutter 3.44.4 (Pages deployment 2d081a6b). The red Cloudflare check above is from the run that started before the command change.

After this merges, the else branch in the dashboard command can be removed.

@TOMOKI977

Copy link
Copy Markdown
Contributor Author

@fercodes @Pericena @XxHugheadxX @moises-cisneros this one needs a review with priority: it is P0 and blocks the Serverpod Cloud deploy (#31, stacked in #115).

All checks are green, including the new docker job and Cloudflare Pages. Most of the diff is generated code and lockfile. The parts worth reading are migration.sql:101-126 (the hand-edited block that preserves rate-limit rows), puls3_server/Dockerfile, .dockerignore and the docker job in ci.yml.

After it merges, everyone needs to upgrade to Flutter 3.44.4 and serverpod_cli 4.0.4. The steps are in docs/operations/serverpod-4-migration.md.

…-upgrade

Brings in the hire and hire_payment tables from #93. The generated protocol
files are regenerated with serverpod_cli 4.0.4 instead of resolved by hand.
The forced upgrade-4-0 migration is removed here because its snapshot predates
migration 20261006210458593; it is recreated in the next commit.
… the hire tables

The forced upgrade-4-0 migration was created before #93 added migration
20261006210458593 with the hire and hire_payment tables, so its snapshot
did not know about them. Recreate it with serverpod_cli 4.0.4 as
20261007214534790-upgrade-4-0 so it now sorts after 20261006210458593 and its
definition includes hire, hire_payment, chain_submission and the Serverpod 4
module tables. The migration does not create, drop or alter the hire tables.

The hand edit is re-applied: rows of
serverpod_auth_idp_rate_limited_request_attempt are copied to a temporary
table before Serverpod drops and recreates it, with nonce mapped to key, and
restored afterwards, so rate-limit history still survives the upgrade.

Refs #110
#93 reads it in HireService without listing it, which fails
scripts/tests/env-inventory.test.sh on main.

Refs #110
scripts/tests/*.test.sh were never run in CI, so main broke the env
inventory (PULS3_HIRE_JOB_DURATION_SECONDS) without a red check. The new
scripts job runs them all and gates the merge.

Refs #110

@moises-cisneros moises-cisneros left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed and tested locally at 93c47ab. No blockers; a few minor comments below.

What I verified

  • Migration: loaded the previous (Serverpod 3) schema into Postgres 16, seeded 5 rows in serverpod_auth_idp_rate_limited_request_attempt, applied migration.sql. All 5 rows kept, same ids, nonce renamed to key, no diff. All four module versions are registered.
  • Image: docker build succeeds and the CI docker job checks pass (server/bin/main executable, production.yaml present, no passwords.yaml).
  • Runtime: the binary starts on Alpine and reports Serverpod 4.0.4 on Dart 3.12.2. On an empty database with --apply-migrations it applies everything, /health/check answers, and there are no target-state warnings.
  • scripts/tests/*.test.sh: all five suites pass.
  • Not run locally: dart test and flutter build web (my toolchain is older than the pins).

Comments

  1. The shell-form ENTRYPOINT ignores arguments. docker run <image> --apply-migrations started the server with applyMigrations: false. It only works by overriding the entrypoint. Inherited from the template, but an exec form or a CMD would be safer.
  2. No automated test runs the migration over pre-existing rate-limit rows. It works (see above), but it is the only hand-edited SQL, so a regression test would be worth a follow-up issue.
  3. The runtime stage uses alpine:latest. Now that the bundle carries native libs, pinning a version would keep the image reproducible.
  4. Scope: cloudflare-pages-build.sh and its test, the scripts CI job and the .env.example additions are unrelated to the Serverpod 4 upgrade. A separate PR would keep this one easy to revert.
  5. prefer_initializing_formals: false disables the rule for the whole package; a local // ignore would be narrower.
  6. docs/operations/serverpod-4-migration.md: worth noting that Serverpod Cloud already runs with applyMigrations: true, so the manual --apply-migrations step applies to local/self-hosted setups only.

@moises-cisneros moises-cisneros left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving after local verification (migration with pre-existing rate-limit rows, image build, container start and /health/check). The non-blocking comments in my previous review still apply as follow-ups.

@moises-cisneros
moises-cisneros merged commit c328902 into main Oct 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: infra CI/CD, deploy, testnet, secrets P0 Blocks a deadline deliverable type: chore Maintenance and tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(infra): upgrade Serverpod 3.4.13 to 4.x for Cloud deployment

2 participants