ci(issue-20): publish Docker image on release tags#153
Conversation
📝 WalkthroughWalkthroughThis pull request introduces comprehensive Docker support for the application. It adds a multi-stage Dockerfile for building and running the app, a GitHub Actions workflow that publishes versioned images to GHCR on releases, a docker-compose setup for local development with dependencies, and supporting configuration and documentation. ChangesDocker containerization and GHCR publishing
🎯 2 (Simple) | ⏱️ ~12 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Dockerfile (1)
17-37:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRun the production container as a non-root user.
The final image currently runs as root, which is avoidable and weakens container isolation.
🔒 Suggested hardening
FROM node:20-bookworm-slim AS prod WORKDIR /app +RUN groupadd -r app && useradd -r -g app app ENV PORT=3000 RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* COPY package.json package-lock.json ./ COPY prisma ./prisma RUN npm ci --omit=dev -COPY --from=build /app/dist ./dist -COPY --from=build /app/prisma ./prisma +COPY --from=build --chown=app:app /app/dist ./dist +COPY --from=build --chown=app:app /app/prisma ./prisma ENV NODE_ENV=production EXPOSE 3000 +USER app CMD ["node", "dist/server.js"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Dockerfile` around lines 17 - 37, The image runs as root; create and use a dedicated non-root user by adding a system group/user (e.g., "app"), chowning the application directory (/app) and any copied files (prisma, dist) to that user, and add a USER app directive before CMD so the container runs as the non-root "app" user; update the Dockerfile sections around the prod stage (where files are installed/copied and before CMD ["node", "dist/server.js"]) to perform the user/group creation, chown of /app, and switch to USER app.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docker-compose.full.yml`:
- Around line 33-35: Replace the hardcoded secret values for WORKER_API_KEY,
STRIPE_SECRET_KEY, and STRIPE_WEBHOOK_SECRET with environment variable
interpolation so runtime secrets can be provided securely (e.g., use
${WORKER_API_KEY}, ${STRIPE_SECRET_KEY}, ${STRIPE_WEBHOOK_SECRET} with sensible
.env defaults or no defaults), update any documentation/README to show required
env variables, and ensure any CI/dev compose overrides or secret managers are
used instead of embedding literal keys in the docker-compose service definition.
- Around line 26-30: Replace the local build with the published GHCR image for
the "app" service: remove or replace the build: . key and add an image: entry
that points to the project GHCR repository with a pinned tag (e.g. image:
ghcr.io/your-org/your-repo:${APP_IMAGE_TAG:-v1.0.0}); keep existing ports and
environment keys intact so compose uses the versioned artifact rather than
rebuilding from source.
In `@Dockerfile`:
- Line 5: Update the apt install commands in the Dockerfile RUN lines (the RUN
invoking apt-get install -y openssl at the current change and the similar
command at the other stage) to add --no-install-recommends to apt-get install so
unnecessary recommended packages are not pulled in; modify the RUN that
currently calls "apt-get update -y && apt-get install -y openssl && rm -rf
/var/lib/apt/lists/*" (and the equivalent on the other stage) to include
"--no-install-recommends" right after "apt-get install" while keeping the update
and cleanup steps intact.
- Around line 34-36: Add a Docker HEALTHCHECK instruction after EXPOSE/CMD that
probes the application's /health endpoint on port 3000 (e.g., using curl or
wget) to return healthy on HTTP 200; configure sensible options such as
--interval=30s --timeout=5s --start-period=10s --retries=3 so orchestrators get
readiness/liveness signals. Ensure the HEALTHCHECK uses a shell form that exits
0 on success and non‑zero on failure and does not override the existing CMD
["node","dist/server.js"].
In `@README.md`:
- Around line 297-301: Update the README entry that currently shows running "npx
prisma migrate deploy" on the host to instruct running migrations inside the
Docker app container instead: replace the host-run instruction for the symbol
"npx prisma migrate deploy" with guidance to run it via docker compose using the
docker-compose.full.yml file and the "app" service (i.e., use docker compose
exec against the app container) so migrations use the internal "postgres"
hostname instead of trying to reach Postgres on the host.
---
Outside diff comments:
In `@Dockerfile`:
- Around line 17-37: The image runs as root; create and use a dedicated non-root
user by adding a system group/user (e.g., "app"), chowning the application
directory (/app) and any copied files (prisma, dist) to that user, and add a
USER app directive before CMD so the container runs as the non-root "app" user;
update the Dockerfile sections around the prod stage (where files are
installed/copied and before CMD ["node", "dist/server.js"]) to perform the
user/group creation, chown of /app, and switch to USER app.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7846baa4-a83a-4165-aac8-548cc160258e
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
.dockerignore.github/workflows/release.ymlDockerfileREADME.mddocker-compose.full.ymlpackage.jsonsrc/payments/providers/stripe/reconciliationService.tstests/integration/stripe/reconciliation.test.tstests/unit/payments/reconciliationService.test.ts
| app: | ||
| build: . | ||
| ports: | ||
| - "${APP_PORT:-3000}:3000" | ||
| environment: |
There was a problem hiding this comment.
Use the published GHCR image for pinned self-hosting.
build: . forces source builds and bypasses the versioned artifact path this PR introduces.
📦 Suggested compose adjustment
app:
- build: .
+ image: ghcr.io/${IMAGE_OWNER:-jonasbaeumer}/agentwallet:${APP_IMAGE_TAG:-edge}
+ pull_policy: always📝 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.
| app: | |
| build: . | |
| ports: | |
| - "${APP_PORT:-3000}:3000" | |
| environment: | |
| app: | |
| image: ghcr.io/${IMAGE_OWNER:-jonasbaeumer}/agentwallet:${APP_IMAGE_TAG:-edge} | |
| pull_policy: always | |
| ports: | |
| - "${APP_PORT:-3000}:3000" | |
| environment: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docker-compose.full.yml` around lines 26 - 30, Replace the local build with
the published GHCR image for the "app" service: remove or replace the build: .
key and add an image: entry that points to the project GHCR repository with a
pinned tag (e.g. image: ghcr.io/your-org/your-repo:${APP_IMAGE_TAG:-v1.0.0});
keep existing ports and environment keys intact so compose uses the versioned
artifact rather than rebuilding from source.
Add multi-stage Dockerfile, docker-compose.full.yml (app + Postgres + Redis), and a GitHub Actions workflow publishing to GHCR on version tags and edge on main. Hardening and docs from review: - Production image runs as a dedicated non-root user; apt installs use --no-install-recommends; curl-based HEALTHCHECK on /health. - Compose reads WORKER_API_KEY, STRIPE_SECRET_KEY, and STRIPE_WEBHOOK_SECRET from the host environment with the same local defaults as before. - README documents running prisma migrate deploy inside the app container. - Move prisma to runtime dependencies so migrate deploy works with npm ci --omit=dev. Closes #20
39661a7 to
6dd69f4
Compare
|
Update
Left CI run for this push: all required jobs green except CodeRabbit still finishing when this was posted. |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 @.github/workflows/release.yml:
- Line 35: The GHCR image reference uses `${{ github.repository_owner }}` which
can be mixed-case and will cause pushes to fail; change the workflow to use a
lowercase owner when constructing the image name (the `images:` value that
currently builds ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}).
Fix by converting `${{ github.repository_owner }}` to lowercase before passing
it to the metadata/action—either use the `lower` filter if your action runner
supports it (e.g., `${{ github.repository_owner | lower }}`) or add a short
shell step that writes a lowercase owner output (e.g., create
`steps.lowercase.outputs.owner_lc`) and then set `images:` to ghcr.io/${{
steps.lowercase.outputs.owner_lc }}/${{ env.IMAGE_NAME }} so `env.IMAGE_NAME`
remains unchanged.
In `@Dockerfile`:
- Around line 34-38: Duplicate COPY of the Prisma directory in the Dockerfile
causes unnecessary redundancy; choose one approach and remove the other: either
delete the host-to-image COPY prisma ./prisma line (the first COPY) and keep
COPY --from=build --chown=app:app /app/prisma ./prisma, or remove the
build-stage COPY --from=build --chown=app:app /app/prisma ./prisma and rely on
the initial COPY prisma ./prisma; ensure only one COPY for prisma remains and
adjust ownership flags as needed to match the chosen copy.
In `@package.json`:
- Line 29: Update the "prisma" dependency value in package.json from "^5.14.0"
to the desired modern version (e.g., "^7.4.2"), run your package manager to
install (npm install or yarn), then run prisma generate to rebuild the client;
afterwards run the test suite and any database migrations against a staging
database to validate compatibility with the new Prisma release and fix any API
changes surfaced by the Prisma client or schema usage in code (search references
to "prisma" client creation/usage to update method signatures if needed).
- Line 29: The package.json lists the Prisma dependency as "prisma": "^5.14.0";
update that version string to the current target (e.g., "^7.8.0") in
package.json, then run your package manager (npm install or pnpm install) to
refresh lockfile and node_modules; after upgrading run Prisma post-install tasks
-- at minimum run npx prisma generate and run your migration/validation commands
(e.g., npx prisma migrate deploy or npx prisma migrate reset in dev) and run the
test suite to catch any breaking changes introduced by the upgrade.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8d0ac566-da1f-4058-967b-1092de020b1a
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (6)
.dockerignore.github/workflows/release.ymlDockerfileREADME.mddocker-compose.full.ymlpackage.json
| - id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the actual repository owner casing from GitHub API
gh api repos/$GITHUB_REPOSITORY --jq '.owner.login'Repository: JonasBaeumer/AgentWallet
Length of output: 217
🏁 Script executed:
cat .github/workflows/release.ymlRepository: JonasBaeumer/AgentWallet
Length of output: 1573
🌐 Web query:
GHCR GitHub Container Registry lowercase image names requirement
💡 Result:
Yes, GHCR requires container image names (repository names in the image reference) to be lowercase. This is due to the Docker/OCI image reference specification enforced by the underlying distribution/reference library, which defines repository name components using the regex [a-z0-9]+ (lowercase letters and digits only) [1][2]. Uppercase letters trigger the error "repository name must be lowercase" [3][4][5][2]. GitHub repository names are case-insensitive [6], so ${{ github.repository }} (e.g., "Owner/Repo") must be lowercased (e.g., "owner/repo") when used in GHCR image tags like ghcr.io/${{ github.repository }}:tag [4][5][7][8]. Official GitHub docs do not explicitly state this but use lowercase examples like ghcr.io/octocat/my-repo:latest [9][10]. Workaround in GitHub Actions: - name: Lowercase repo run: echo "IMAGE=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - uses: docker/build-push-action@v5 with: tags: ${{ env.IMAGE }}:latest [4][5].
Citations:
- 1: https://github.com/distribution/reference/blob/main/regexp.go
- 2: https://github.com/distribution/distribution/blob/d0deff9cd6c2b8c82c6f3d1c713af51df099d07b/reference/reference.go
- 3: https://stackoverflow.com/questions/48522615/docker-error-invalid-reference-format-repository-name-must-be-lowercase
- 4: https://devops.stackexchange.com/questions/20249/github-actions-pushing-to-github-container-registry-ghcr-returns-repository-n
- 5: Repository/image name should be lowercased automatically. docker/build-push-action#37
- 6: github repository names are, in fact, not case-sensitive github/docs#32838
- 7: Normalize GHCR references to lowercase repository names rucio/rucio#8006
- 8: Job fails if repo name includes UPPER CASE leters macbre/push-to-ghcr#12
- 9: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry
- 10: https://docs.github.com/en/packages/guides/pushing-and-pulling-docker-images
GHCR requires lowercase image names; this workflow will fail with mixed-case owner.
The image reference uses ${{ github.repository_owner }}, which is "JonasBaeumer" (mixed case). GHCR enforces lowercase in repository names (per OCI spec), causing push failures. The env.IMAGE_NAME is already lowercase, but the owner is not.
The proposed fix using ${{ github.repository_owner_id }} is incorrect—that variable is numeric and unsuitable for image names. Instead, lowercase the owner in a prior step:
- id: meta
uses: docker/metadata-action@v5
with:
- images: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
+ images: ghcr.io/${{ github.repository_owner | lower }}/${{ env.IMAGE_NAME }}Or if using older action versions without the lower filter, use a shell step:
- id: lowercase
run: echo "owner_lc=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ steps.lowercase.outputs.owner_lc }}/${{ env.IMAGE_NAME }}🤖 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 @.github/workflows/release.yml at line 35, The GHCR image reference uses `${{
github.repository_owner }}` which can be mixed-case and will cause pushes to
fail; change the workflow to use a lowercase owner when constructing the image
name (the `images:` value that currently builds ghcr.io/${{
github.repository_owner }}/${{ env.IMAGE_NAME }}). Fix by converting `${{
github.repository_owner }}` to lowercase before passing it to the
metadata/action—either use the `lower` filter if your action runner supports it
(e.g., `${{ github.repository_owner | lower }}`) or add a short shell step that
writes a lowercase owner output (e.g., create
`steps.lowercase.outputs.owner_lc`) and then set `images:` to ghcr.io/${{
steps.lowercase.outputs.owner_lc }}/${{ env.IMAGE_NAME }} so `env.IMAGE_NAME`
remains unchanged.
| COPY prisma ./prisma | ||
| RUN npm ci --omit=dev | ||
|
|
||
| COPY --from=build --chown=app:app /app/dist ./dist | ||
| COPY --from=build --chown=app:app /app/prisma ./prisma |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Minor: Prisma directory is copied twice.
Line 34 copies prisma from the host, then line 38 copies it again from the build stage. Since the build stage doesn't modify the prisma directory, you can remove line 38 and rely on line 34, or remove line 34 and keep only the build-stage copy.
♻️ Simplify by removing the duplicate copy
Option 1: Keep host copy, remove build-stage copy:
COPY package.json package-lock.json ./
COPY prisma ./prisma
RUN npm ci --omit=dev
COPY --from=build --chown=app:app /app/dist ./dist
-COPY --from=build --chown=app:app /app/prisma ./prismaOption 2: Remove host copy, keep build-stage copy:
COPY package.json package-lock.json ./
-COPY prisma ./prisma
RUN npm ci --omit=dev
COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=build --chown=app:app /app/prisma ./prisma📝 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.
| COPY prisma ./prisma | |
| RUN npm ci --omit=dev | |
| COPY --from=build --chown=app:app /app/dist ./dist | |
| COPY --from=build --chown=app:app /app/prisma ./prisma | |
| COPY package.json package-lock.json ./ | |
| COPY prisma ./prisma | |
| RUN npm ci --omit=dev | |
| COPY --from=build --chown=app:app /app/dist ./dist |
🤖 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 34 - 38, Duplicate COPY of the Prisma directory in
the Dockerfile causes unnecessary redundancy; choose one approach and remove the
other: either delete the host-to-image COPY prisma ./prisma line (the first
COPY) and keep COPY --from=build --chown=app:app /app/prisma ./prisma, or remove
the build-stage COPY --from=build --chown=app:app /app/prisma ./prisma and rely
on the initial COPY prisma ./prisma; ensure only one COPY for prisma remains and
adjust ownership flags as needed to match the chosen copy.
| "grammy": "^1.42.0", | ||
| "ioredis": "^5.10.1", | ||
| "pino": "^10.3.1", | ||
| "prisma": "^5.14.0", |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 🏗️ Heavy lift
Consider upgrading Prisma to the latest version.
The current version 5.14.0 is from May 2024, while Prisma 7.3.0 was released on January 21, 2026, and version 7.4.2 was released on February 27, 2026. Prisma ORM 7 delivers major performance gains, a Rust-free client, and a streamlined developer experience. Upgrading would provide nearly 2 years of bug fixes, security patches, and performance improvements.
🤖 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 `@package.json` at line 29, Update the "prisma" dependency value in
package.json from "^5.14.0" to the desired modern version (e.g., "^7.4.2"), run
your package manager to install (npm install or yarn), then run prisma generate
to rebuild the client; afterwards run the test suite and any database migrations
against a staging database to validate compatibility with the new Prisma release
and fix any API changes surfaced by the Prisma client or schema usage in code
(search references to "prisma" client creation/usage to update method signatures
if needed).
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🌐 Web query:
What is the latest version of Prisma ORM and are there any security advisories for version 5.14.0?
💡 Result:
The latest version of Prisma ORM is 7.8.0 [1][2][3]. There are no security advisories specifically for version 5.14.0 [4][5]. The 5.14.0 release notes mention performance fixes and bug fixes but no security vulnerabilities [4]. Recent security issues in Prisma relate to dependencies like hono in @prisma/dev (used in development, not runtime Prisma Client) and are fixed in later versions [6][7][8][9]. The GitHub security overview shows only an old low-severity advisory from 2021 [5].
Citations:
- 1: https://github.com/prisma/prisma/releases
- 2: https://www.npmjs.com/package/prisma
- 3: https://github.com/prisma/prisma/releases/tag/7.8.0
- 4: https://github.com/prisma/prisma/releases/tag/5.14.0
- 5: https://github.com/prisma/prisma/security
- 6: Security Vulnerabilities in @prisma/dev dependencies (hono) prisma/prisma#29027
- 7: Security Vulnerabilities in
@prisma/devdependencies (hono,valibot) prisma/prisma#28752 - 8: Security vulnerability in the Hono dependency prisma/prisma#28568
- 9: New security vulnerability in @prisma/dev dependency 'hono' prisma/prisma#29123
Consider updating to the latest Prisma version for performance improvements and bug fixes.
Prisma 5.14.0 (May 2024) is 2 years old; the latest version is 7.8.0. While version 5.14.0 has no known security vulnerabilities, updating provides access to performance improvements and bug fixes released over the past two years.
🤖 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 `@package.json` at line 29, The package.json lists the Prisma dependency as
"prisma": "^5.14.0"; update that version string to the current target (e.g.,
"^7.8.0") in package.json, then run your package manager (npm install or pnpm
install) to refresh lockfile and node_modules; after upgrading run Prisma
post-install tasks -- at minimum run npx prisma generate and run your
migration/validation commands (e.g., npx prisma migrate deploy or npx prisma
migrate reset in dev) and run the test suite to catch any breaking changes
introduced by the upgrade.
|
Self-review pass complete — ready for maintainer review. What I checked:
Suggested review focus: whether the release workflow should publish |
Summary
Dockerfilethat successfully builds/runs the app in a container.docker-compose.full.yml(app + Postgres + Redis) for one-command local self-hosting.edgeonmain).Test plan
npm test -- --testPathPattern=tests/unitdocker build .APP_PORT=3001 docker compose -f docker-compose.full.yml up --buildandGET /healthCloses #20
Summary by CodeRabbit
New Features
Chores
Documentation