Skip to content

Deploy staging API from GHCR by digest - #995

Merged
thomasluizon merged 3 commits into
mainfrom
chore/ticket-1874-staging-api-image
Oct 29, 2026
Merged

thomasluizon merged 3 commits into
mainfrom
chore/ticket-1874-staging-api-image

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Tracks thomasluizon/orbit-tickets#1874.

Staging releases build the selected API commit in GitHub Actions and deploy its immutable GHCR digest, avoiding Render Docker builds when pipeline minutes are exhausted. Production's release job is byte-for-byte unchanged.

Approach

  • .github/workflows/release.yml retains the workflow checkout for release helpers, checks out the prepared SHA separately, verifies identity and branch ancestry, then builds Dockerfile with the Docker driver and pushes ghcr.io/thomasluizon/orbit-api:<sha> with GITHUB_TOKEN and packages: write. The digest reaches the summary before deployment. Render must return HTTP 201 with a deploy ID and matching image.ref; that deploy must become live, public health must report Healthy with the planned commit, and the final live image must match. The first Git-backed service rejection explains the required coordinator source switch.
  • Dockerfile bakes API_COMMIT_SHA into RENDER_GIT_COMMIT, the existing /health commit source. No API contract changes. The build regenerated OpenAPI without a content change.
  • infra/staging.tf, infra/variables.tf and infra/example.tfvars declare the public API image, seed staging_api_digest beside the web digest and ignore later digest changes. Identity, free plan, domains, health check and environment links remain declared as before.
  • .github/workflows/staging-reseed.yml must change with the service source: its commit-only redeploy cannot run on an image-backed service. It now redeploys the currently live immutable image, verifies the HTTP 201 image identity and follows the returned deploy. .github/scripts/staging-reseed-image.test.mjs covers this required dependency.
  • README.md and infra/README.md document the release flow and coordinator rollout. Existing release behavior tests under tools/__tests__ were adapted for image deployments; no harness implementation changed.

External interface evidence

Read the current Render OpenAPI specification, linked from Render's API spec page. Re-derive with curl -fsSL https://api-docs.render.com/openapi/render-public-api-1.json:

  • paths["/services/{serviceId}/deploys"].post.requestBody declares imageUrl for an image-backed service, mutually exclusive with commitId. Responses declare HTTP 201 as components.schemas.deploy and HTTP 202 as queued without a response body. See also Trigger deploy.
  • components.schemas.deploy.properties declares string id, image.ref and status; status references deployStatus, which declares every waiting, success and failure status used here. The deploy detail GET returns that same schema. See Retrieve deploy.
  • The list GET returns deployList, whose deployWithCursor items wrap a deploy. This confirms .[0].deploy.image.ref for live-image verification and reseed. Its existing status and limit query parameters are retained.
  • The new image fixtures contain only fields confirmed by those schemas. No live Render service call or write was made, as the coordinator explicitly owns the transition.
  • Read docker/build-push-action v7 action.yml: outputs.digest is the image digest consumed as steps.image.outputs.digest. The web workflow was read from orbit-ui-mobile origin/main as ordered; the second repository was not modified.

Assumptions

  • Keep the selected build source in release-source rather than replacing the trusted workflow checkout, preserving the existing health verifier and Render read helpers at the dispatch commit.
  • Include the dependent reseed redeploy change in this ticket rather than leave a commit-only operation against the new image-backed staging service.
  • Require an actual HTTP 201 on the staging image POST and stop for operator inspection on ambiguous acceptance rather than use the shared commit-based reconciliation helper; production retains that helper unchanged.

Test evidence

  • Before edits: node --test tools/__tests__/release-selection.test.mjs tools/__tests__/release-render.test.mjs tools/__tests__/release-health.test.mjs passed all 164 existing tests while the commit-backed staging path was present.
  • Before the workflow fix: node --test --test-name-pattern='staging: sends only' tools/__tests__/release-render.test.mjs failed the new staging: sends only the immutable image and follows its returned deploy assertion because the actual POST contained commitId instead of imageUrl. After the fix the same command passed. The complete adapted release suite passed all 137 tests.
  • Before the reseed fix: node --test .github/scripts/staging-reseed.test.mjs passed all 91 unchanged tests. node --test --test-name-pattern='reseed redeploys' .github/scripts/staging-reseed-image.test.mjs then failed with exit 4 from the old jq commit lookup on a schema-derived image deploy without a Git commit. After the fix, node --test .github/scripts/staging-reseed-image.test.mjs .github/scripts/staging-reseed.test.mjs passed all 95 tests.
  • dotnet build Orbit.slnx and final dotnet build Orbit.slnx --no-restore: exit 0, zero errors.
  • dotnet test tests/Orbit.Domain.Tests: exit 0, 1,215 passed. dotnet test tests/Orbit.Application.Tests: exit 0, 9,130 passed. dotnet test tests/Orbit.Infrastructure.Tests: exit 0, 6,598 passed and 14 skipped. dotnet test tests/Orbit.Analyzers.Tests: exit 0, 40 passed.
  • Both changed workflows pass actionlint 1.7.12 with only the repository's existing concurrency.queue compatibility exclusion.
  • terraform fmt -check infra/staging.tf infra/variables.tf infra/example.tfvars passes. terraform validate was not run because providers are not initialized in this worktree. No real-state plan or apply was run.
  • Production job byte comparison, git diff --check, dash checks and commit hooks passed. Live build avoidance, Render pull access and public health rollout are coordinator verification after merge.

Manual steps

  1. After merge, in the API repository Actions > Release API, dispatch from main with environment=staging, branch=main and the planned full sha. The first run pushes the image before the source switch; copy its digest from the summary even if deployment fails.
  2. In GitHub thomasluizon > Packages > orbit-api > Package settings > Change package visibility, set the package to public. An unauthenticated pull of ghcr.io/thomasluizon/orbit-api@<published digest> proves access; no new secret or registry credential is required.
  3. The coordinator switches existing Render service orbit-api-staging (srv-dasotg8473hc739a5gjg) to that public image through the Render API, preserving ID, custom domains, environment groups, free plan and /health. This uses the authorized workaround for provider issue #80; do not apply a Terraform source update through the affected free-service provider.
  4. Set staging_api_digest in ignored infra/local.tfvars to the published sha256:..., refresh Terraform state, then review a plan targeted only to render_web_service.staging_api. It must show no change and no replacement. The worker has not run Terraform against real state.
  5. Rerun Actions > Release API with the same planned staging SHA. Verify the Render deploy reaches live with no image build and https://api-staging.useorbit.org/health reports status: Healthy and that exact commit. Existing staging secret RENDER_API_KEY and repository variable RENDER_STAGING_SERVICE_ID remain the release inputs. Production rollout is outside this ticket.

pullfrog[bot]
pullfrog Bot previously approved these changes Oct 29, 2026

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes Reviewed the staging API image-release path, dependent database-reseed deployment, Terraform source declaration, Docker commit metadata, and rollout documentation.

  • Immutable staging releases: Builds the verified selected commit in GitHub Actions, publishes to GHCR, records the digest, and deploys that exact image through Render while leaving production release logic unchanged.
  • Deployment verification: Requires an identified HTTP 201 response with matching image reference, follows that deploy to live, and checks public health and the final live image before recording success.
  • Reseed compatibility: Redeploys the currently live immutable image after database replacement without depending on Git-backed deploy metadata.
  • Infrastructure and rollout: Declares the staging API image source and initial digest, preserves existing service configuration, and documents the coordinator-owned source transition and public-registry setup.
  • Validation: All 232 release/reseed regression tests and 52 infrastructure tests passed locally. Render request/response fields and Docker action outputs were checked against their upstream definitions. Live registry pulls, runtime commit reporting, and the real-state Terraform plan remain rollout verification; no live infrastructure was changed during review.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

SonarCloud S7637 requires a full commit SHA for each action dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes Reviewed commit 033db5c7 since the previous Pullfrog review, covering the three Docker action pins in .github/workflows/release.yml.

  • Pinned staging image actions: Replaced moving major-version references with full commit SHAs for Docker setup-buildx, login, and build-push without changing workflow inputs or deployment logic.
  • Verified upstream identities: Confirmed each SHA matches its annotated upstream release tag (v4.4.1, v4.7.0, and v7.4.0) and checked the pinned action definitions for the configured inputs and image digest output.
  • Validated release behavior: Reran all 137 release-selection, Render-deployment, and public-health regression tests successfully. No live image build or Render deployment was performed.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit 74c6e4f into main Oct 29, 2026
25 checks passed
@thomasluizon
thomasluizon deleted the chore/ticket-1874-staging-api-image branch October 29, 2026 04:12
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