feat: Add Docker image for capiscio/guard sidecar#31
Conversation
- Add multi-stage Dockerfile using distroless for minimal, secure image - Add GitHub Action to build and push on release tags (linux/amd64, linux/arm64) - Add docker-build, docker-run, docker-push targets to Makefile - Update README with Docker usage documentation The Guard sidecar is a reverse proxy that enforces badge validity before forwarding traffic to your agent. Now deployable via: docker pull capiscio/guard docker run -p 8080:8080 capiscio/guard gateway start --port 8080 --target http://your-agent:3000
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Adds first-class Docker distribution for the CapiscIO Guard sidecar (container image + CI build/publish flow) and documents how to run it.
Changes:
- Introduces a multi-stage
Dockerfiletargeting a small distroless runtime image. - Adds a GitHub Actions workflow to build/push multi-arch images on version tags.
- Adds Makefile targets and README documentation for local build/run.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
README.md |
Adds Docker usage and compose examples for running Guard as a container. |
Makefile |
Adds docker-build, docker-run, and docker-push helper targets. |
Dockerfile |
Implements multi-stage build and distroless runtime for the capiscio/guard image. |
.github/workflows/docker.yml |
Builds and pushes multi-arch images on tag releases and attempts to verify pushed images. |
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ | ||
| -ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \ |
There was a problem hiding this comment.
The builder stage hard-codes GOARCH=amd64, but the workflow builds linux/amd64,linux/arm64. This will produce an amd64 binary inside the arm64 image (leading to exec format error at runtime). Use BuildKit-provided TARGETARCH/TARGETOS (or omit GOARCH and rely on --platform) so each platform builds the correct binary.
| -ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \ | ||
| -o /capiscio \ |
There was a problem hiding this comment.
The -ldflags set -X main.commit=..., but there is no commit string variable in the main package (cmd/capiscio/main.go only defines var version = ...). This causes the Docker build to fail at link time. Either add a var commit = "..." in the main package or drop the -X main.commit flag.
| @@ -1,7 +1,38 @@ | |||
| .PHONY: all build-cli build-python test clean proto docs | |||
| .PHONY: all build-cli build-python test clean proto docs docker docker-build docker-run | |||
There was a problem hiding this comment.
docker-push is defined below but not listed in .PHONY, so a file named docker-push could prevent this target from running. Add docker-push to the .PHONY list.
| .PHONY: all build-cli build-python test clean proto docs docker docker-build docker-run | |
| .PHONY: all build-cli build-python test clean proto docs docker docker-build docker-run docker-push |
| docker-run: ## Run Docker image locally (example) | ||
| @echo "Running $(DOCKER_IMAGE):latest..." | ||
| @echo "Usage: make docker-run TARGET=http://localhost:3000" | ||
| docker run --rm -p 8080:8080 $(DOCKER_IMAGE):latest \ | ||
| gateway start --port 8080 --target $(TARGET) --registry-url https://registry.capisc.io |
There was a problem hiding this comment.
docker-run passes --target $(TARGET) but TARGET has no default, so running make docker-run without setting TARGET will pass an empty value and likely fail. Consider setting a sensible default (e.g., TARGET ?= http://host.docker.internal:3000) or erroring out when TARGET is unset.
| # Verify the image works | ||
| verify: | ||
| name: Verify Image | ||
| needs: build-and-push | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Pull and test image | ||
| run: | | ||
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.version || 'latest' }} | ||
| docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.version || 'latest' }} --version |
There was a problem hiding this comment.
The verify job pulls ${{ needs.build-and-push.outputs.version || 'latest' }}, but build-and-push does not define any job outputs, so this will always fall back to latest. For workflow_dispatch (where latest tagging is disabled), this means verify may test an old image instead of the one just built. Expose the version from the build job (e.g., map it from steps.version.outputs.version) and use that tag here.
| --target http://host.docker.internal:3000 \ | ||
| --registry-url https://registry.capisc.io | ||
| ``` | ||
|
|
There was a problem hiding this comment.
The quick-start example uses --target http://host.docker.internal:3000, which isn’t available by default on many Linux Docker setups. Consider adding a short Linux note (e.g., using --add-host=host.docker.internal:host-gateway or using a user-defined network / container name) to prevent copy/paste failures.
| > **Note for Linux users:** `host.docker.internal` is not available by default on many Linux Docker setups. | |
| > You can either: | |
| > - add `--add-host=host.docker.internal:host-gateway` to the `docker run` command so the container can reach your host at `http://host.docker.internal:3000`, or | |
| > - run your agent in another container on a user-defined Docker network and use the container name as the `--target` (as shown in the Docker Compose example below). |
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
For workflow_dispatch, the workflow accepts an input tag but actions/checkout doesn’t check out that tag/commit (no ref: set). This will build whatever is on the default branch while tagging/pushing it as the requested release tag. Set with: ref: ${{ inputs.tag }} (or resolve the tag to a SHA) when github.event_name == 'workflow_dispatch'.
| uses: actions/checkout@v4 | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} |
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ | ||
| -ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \ | ||
| -o /capiscio \ |
There was a problem hiding this comment.
-ldflags injects -X main.commit=${COMMIT}, but there is no commit variable in package main (cmd/capiscio/main.go only defines version). This will make go build fail in Docker and CI. Either add var commit = "unknown" in the main package (and optionally include it in the cobra Version output), or remove the main.commit ldflag and corresponding build-arg usage.
| // Trust level for badges (RFC-002 §5) | ||
| // NOTE: Proto enum ordinals (1-5) map to RFC-002 level strings ("0"-"4") | ||
| // The badge JWT `vc.credentialSubject.level` uses the RFC string values | ||
| enum TrustLevel { | ||
| TRUST_LEVEL_UNSPECIFIED = 0; | ||
| TRUST_LEVEL_SELF_SIGNED = 1; // Self-signed (Level 0, did:key) | ||
| TRUST_LEVEL_DV = 2; // Domain Validated (Level 1) | ||
| TRUST_LEVEL_OV = 3; // Organization Validated (Level 2) | ||
| TRUST_LEVEL_EV = 4; // Extended Validated (Level 3) | ||
| TRUST_LEVEL_CV = 5; // Community Vouched (Level 4) | ||
| TRUST_LEVEL_SELF_SIGNED = 1; // RFC-002 Level "0": Self-Signed (SS) - did:key, iss == sub | ||
| TRUST_LEVEL_DV = 2; // RFC-002 Level "1": Registered (REG) - account registration | ||
| TRUST_LEVEL_OV = 3; // RFC-002 Level "2": Domain Validated (DV) - DNS/HTTP proof | ||
| TRUST_LEVEL_EV = 4; // RFC-002 Level "3": Organization Validated (OV) - legal entity | ||
| TRUST_LEVEL_CV = 5; // RFC-002 Level "4": Extended Validated (EV) - security audit |
There was a problem hiding this comment.
The TrustLevel enum value names no longer match their documented meanings (e.g., TRUST_LEVEL_DV is commented as Registered/REG, while TRUST_LEVEL_OV is commented as Domain Validated/DV). This is confusing for API consumers and easy to misuse. Consider adding correctly named aliases (e.g., TRUST_LEVEL_REG = 2, TRUST_LEVEL_DV = 3, etc.) with option allow_alias = true, and/or adjust names/comments so they consistently describe the same level.
| # Test locally | ||
| make docker-run TARGET=http://localhost:3000 |
There was a problem hiding this comment.
The local test command suggests make docker-run TARGET=http://localhost:3000, but localhost inside the Guard container points back to the container, not your host agent, so this example will usually fail. Consider changing the example to TARGET=http://host.docker.internal:3000 (and add a note for Linux users, e.g. --add-host=host.docker.internal:host-gateway), or recommend using the Docker Compose example for local testing.
| # Test locally | |
| make docker-run TARGET=http://localhost:3000 | |
| # Test locally (container -> host agent) | |
| # macOS/Windows: host.docker.internal resolves to the host | |
| # Linux: ensure your docker run includes --add-host=host.docker.internal:host-gateway | |
| make docker-run TARGET=http://host.docker.internal:3000 |
| ) | ||
|
|
||
| var version = "2.3.1" | ||
| var version = "2.4.0" |
There was a problem hiding this comment.
The PR description focuses on adding Docker support, but this change also bumps the CLI version string to 2.4.0. If 2.4.0 is not being released as part of this PR, consider keeping the existing version and letting the release/tag drive the version, or update the PR description/release notes to reflect the version bump.
| docker-run: ## Run Docker image locally (example) | ||
| @echo "Running $(DOCKER_IMAGE):latest..." | ||
| @echo "Usage: make docker-run TARGET=http://localhost:3000" | ||
| docker run --rm -p 8080:8080 $(DOCKER_IMAGE):latest \ | ||
| gateway start --port 8080 --target $(TARGET) --registry-url https://registry.capisc.io |
There was a problem hiding this comment.
make docker-run prints an example using TARGET=http://localhost:3000, but from inside the container localhost refers to the container itself, not the host. This will typically fail unless the user runs with host networking. Consider defaulting TARGET to http://host.docker.internal:3000 (with a Linux note or --add-host=host.docker.internal:host-gateway) or erroring out when TARGET is empty/localhost to avoid a confusing failure.
Summary
Adds Docker support for the CapiscIO Guard sidecar, enabling easy deployment of the security gateway as a container.
Changes
Usage
After merge and release:
Setup Required
Before the first release, add these secrets to the repository:
DOCKERHUB_USERNAMEDOCKERHUB_TOKENTesting
Related
This fulfills the marketing promise on capisc.io for
docker pull capiscio/guard.