Skip to content

feat: restrict Docker image publishing to releases only#37

Merged
ThePlenkov merged 4 commits intomainfrom
copilot/build-docker-image-ci-workflow
Mar 2, 2026
Merged

feat: restrict Docker image publishing to releases only#37
ThePlenkov merged 4 commits intomainfrom
copilot/build-docker-image-ci-workflow

Conversation

Copy link
Contributor

Copilot AI commented Mar 2, 2026

Docker builds were triggering on every push to main and every PR, publishing unvalidated images automatically. The workflow should follow the same release-gated pattern as publish.yml.

docker.yml

  • Replace push/pull_request triggers with workflow_dispatch + workflow_call (mirrors publish.yml)
  • Add "Resolve ref" step that checks out the specified version tag or the latest release — fails hard if no release exists (no main fallback)
  • Remove branch/PR tag types from metadata; push: true unconditionally since the workflow is now explicit-only

release.yml

  • Add docker job that calls docker.yml via workflow_call after the release job, using the released tag — runs on every non-dry-run release:
docker:
  name: Build and push Docker image
  needs: release
  if: ${{ !inputs.dry_run }}
  uses: ./.github/workflows/docker.yml
  with:
    version: ${{ needs.release.outputs.tag }}
  permissions:
    contents: read
    packages: write

Unlike publish, the Docker job is not gated by inputs.publish — pushing an image is the natural outcome of any real release.


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

- Dockerfile: multi-stage build (builder + slim runner)
  - Stage 1 builds all packages from source via bun + npx nx
  - Stage 2 copies built artifacts + node_modules, exposes adt via PATH
- .github/workflows/docker.yml: triggers on push/tags/PRs, pushes to GHCR
- .dockerignore: excludes node_modules, dist, .git, dev tooling

Co-authored-by: ThePlenkov <6381507+ThePlenkov@users.noreply.github.com>
Copilot AI changed the title [WIP] Add CI workflow to build Docker image with global packages feat: CI workflow to build and publish Docker image from source Mar 2, 2026
- docker.yml: replace push/PR triggers with workflow_dispatch + workflow_call
  (mirrors publish.yml pattern); always push; remove branch/PR tag types;
  fail if no release exists (remove main fallback)
- release.yml: add docker job that calls docker.yml after release (same
  pattern as publish job), skipped on dry runs

Co-authored-by: ThePlenkov <6381507+ThePlenkov@users.noreply.github.com>
Copilot AI changed the title feat: CI workflow to build and publish Docker image from source feat: restrict Docker image publishing to releases only Mar 2, 2026
@nx-cloud
Copy link
Contributor

nx-cloud bot commented Mar 2, 2026

View your CI Pipeline Execution ↗ for commit 8524633

Command Status Duration Result
nx affected -t lint test build e2e-ci --verbose... ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-02 11:05:02 UTC

@ThePlenkov ThePlenkov marked this pull request as ready for review March 2, 2026 11:04
@ThePlenkov ThePlenkov merged commit 7171e32 into main Mar 2, 2026
2 checks passed
@ThePlenkov ThePlenkov deleted the copilot/build-docker-image-ci-workflow branch March 2, 2026 11:04
@qodo-code-review
Copy link

Review Summary by Qodo

Add Docker image CI workflow and release-gated publishing

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add Docker image CI workflow with release-gated publishing
• Create multi-stage Dockerfile for optimized builds
• Integrate Docker job into release workflow
• Restrict Docker builds to explicit releases only
Diagram
flowchart LR
  Release["Release triggered"]
  Release -- "calls" --> Docker["docker.yml workflow"]
  Docker -- "resolves" --> Ref["Release tag/version"]
  Ref -- "builds" --> Image["Docker image"]
  Image -- "pushes to" --> GHCR["GitHub Container Registry"]
Loading

Grey Divider

File Changes

1. Dockerfile ✨ Enhancement +43/-0

Multi-stage Dockerfile for optimized builds

• Multi-stage build: builder stage compiles all packages via bun and nx
• Runner stage copies built artifacts and node_modules for lean image
• Exposes workspace binaries (adt, adt-codegen) via PATH
• Uses node:24-bookworm for builder, node:24-bookworm-slim for runner

Dockerfile


2. .dockerignore ⚙️ Configuration changes +44/-0

Docker build context exclusion rules

• Excludes build artifacts (node_modules, dist, .nx)
• Excludes source control and CI/CD configuration files
• Excludes dev tooling and documentation
• Excludes lockfiles except bun.lock and miscellaneous config files

.dockerignore


3. .github/workflows/docker.yml ✨ Enhancement +72/-0

Release-gated Docker build and push workflow

• Implements workflow_dispatch and workflow_call triggers (release-gated)
• Resolves version tag from input or latest release (fails if none exists)
• Checks out specified version and builds Docker image
• Pushes to GHCR with semantic versioning and SHA tags

.github/workflows/docker.yml


View more (1)
4. .github/workflows/release.yml ✨ Enhancement +11/-0

Integrate Docker job into release workflow

• Adds docker job that calls docker.yml workflow after release
• Passes released tag version to docker.yml
• Skips Docker build on dry-run releases
• Grants packages:write permission for GHCR push

.github/workflows/release.yml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 2, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Plugins not enabled in image 🐞 Bug ✓ Correctness
Description
The Docker runtime image does not include the repo’s adt.config.ts, so adt won’t auto-load the
plugin commands (atc/export/codegen) even though the image claims they’re available. Users will get
a reduced CLI feature set unless they provide their own config at runtime.
Code

Dockerfile[R30-43]

+WORKDIR /app
+
+# Copy the built workspace packages (dist/ directories are now populated)
+COPY --from=builder /build/packages    ./packages
+# Copy the workspace node_modules so @abapify/* symlinks resolve correctly
+# and third-party runtime deps (commander, axios, etc.) are present
+COPY --from=builder /build/node_modules ./node_modules
+# Copy the root package.json so npm workspace resolution works at runtime
+COPY --from=builder /build/package.json ./
+
+# Expose workspace binaries (adt, adt-codegen, …) as global commands
+ENV PATH="/app/node_modules/.bin:$PATH"
+
+CMD ["adt", "--help"]
Evidence
adt-cli loads command plugins from adt.config.ts by default (searching cwd/parents). The repo
root has an adt.config.ts that enables key plugins, but the Dockerfile runner stage copies only
packages/, node_modules/, and package.json into the final image, omitting the config file—so
plugins won’t be loaded by default in the container.

Dockerfile[30-43]
adt.config.ts[10-20]
packages/adt-cli/src/lib/cli.ts[118-210]
packages/adt-cli/src/lib/plugin-loader.ts[44-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The built Docker runtime image omits the repository’s `adt.config.ts`, so `adt-cli` won’t load the plugin commands (atc/export/codegen) by default.

## Issue Context
`adt-cli` searches for `adt.config.ts` in the current directory/parents and uses it to register plugin commands. The repo root has a config enabling core plugins, but the final image only copies `packages/`, `node_modules/`, and `package.json`.

## Fix Focus Areas
- Dockerfile[30-43]
- adt.config.ts[10-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Arbitrary refs can be published 🐞 Bug ⛨ Security
Description
docker.yml accepts an arbitrary version input and uses it directly as the checkout ref, while
always pushing to GHCR. This means a maintainer can accidentally publish an image built from a
branch/SHA (not a release), which undermines the PR’s “releases only” publishing intent.
Code

.github/workflows/docker.yml[R25-72]

+      - name: Resolve ref
+        id: resolve-ref
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          INPUT_VERSION: ${{ inputs.version }}
+        run: |
+          if [ -n "$INPUT_VERSION" ]; then
+            resolved="$INPUT_VERSION"
+          else
+            resolved=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName -q .tagName)
+          fi
+          echo "Resolved ref: ${resolved}"
+          echo "ref=${resolved}" >> "$GITHUB_OUTPUT"
+
+      - name: Checkout repository
+        uses: actions/checkout@v6
+        with:
+          ref: ${{ steps.resolve-ref.outputs.ref }}
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Log in to GitHub Container Registry
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Extract Docker metadata
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ghcr.io/${{ github.repository }}
+          tags: |
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=sha
+
+      - name: Build and push
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
Evidence
The workflow treats inputs.version as the git ref to checkout and build, with no validation that
it corresponds to a release tag. The job then always pushes (push: true), so a non-release ref
input would still publish an image.

.github/workflows/docker.yml[4-43]
.github/workflows/docker.yml[64-72]
.github/workflows/release.yml[105-114]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Docker publishing workflow still allows publishing from arbitrary git refs via the `version` input (branch names / SHAs), while unconditionally pushing to GHCR.

## Issue Context
This PR’s goal is “releases only” Docker publishing. However, `inputs.version` is used directly as the checkout ref with no validation.

## Fix Focus Areas
- .github/workflows/docker.yml[25-72]
- .github/workflows/release.yml[105-114]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +30 to +43
WORKDIR /app

# Copy the built workspace packages (dist/ directories are now populated)
COPY --from=builder /build/packages ./packages
# Copy the workspace node_modules so @abapify/* symlinks resolve correctly
# and third-party runtime deps (commander, axios, etc.) are present
COPY --from=builder /build/node_modules ./node_modules
# Copy the root package.json so npm workspace resolution works at runtime
COPY --from=builder /build/package.json ./

# Expose workspace binaries (adt, adt-codegen, …) as global commands
ENV PATH="/app/node_modules/.bin:$PATH"

CMD ["adt", "--help"]

Choose a reason for hiding this comment

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

Action required

1. Plugins not enabled in image 🐞 Bug ✓ Correctness

The Docker runtime image does not include the repo’s adt.config.ts, so adt won’t auto-load the
plugin commands (atc/export/codegen) even though the image claims they’re available. Users will get
a reduced CLI feature set unless they provide their own config at runtime.
Agent Prompt
## Issue description
The built Docker runtime image omits the repository’s `adt.config.ts`, so `adt-cli` won’t load the plugin commands (atc/export/codegen) by default.

## Issue Context
`adt-cli` searches for `adt.config.ts` in the current directory/parents and uses it to register plugin commands. The repo root has a config enabling core plugins, but the final image only copies `packages/`, `node_modules/`, and `package.json`.

## Fix Focus Areas
- Dockerfile[30-43]
- adt.config.ts[10-20]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +25 to +72
- name: Resolve ref
id: resolve-ref
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ -n "$INPUT_VERSION" ]; then
resolved="$INPUT_VERSION"
else
resolved=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName -q .tagName)
fi
echo "Resolved ref: ${resolved}"
echo "ref=${resolved}" >> "$GITHUB_OUTPUT"

- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ steps.resolve-ref.outputs.ref }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

Choose a reason for hiding this comment

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

Action required

2. Arbitrary refs can be published 🐞 Bug ⛨ Security

docker.yml accepts an arbitrary version input and uses it directly as the checkout ref, while
always pushing to GHCR. This means a maintainer can accidentally publish an image built from a
branch/SHA (not a release), which undermines the PR’s “releases only” publishing intent.
Agent Prompt
## Issue description
The Docker publishing workflow still allows publishing from arbitrary git refs via the `version` input (branch names / SHAs), while unconditionally pushing to GHCR.

## Issue Context
This PR’s goal is “releases only” Docker publishing. However, `inputs.version` is used directly as the checkout ref with no validation.

## Fix Focus Areas
- .github/workflows/docker.yml[25-72]
- .github/workflows/release.yml[105-114]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.

2 participants