Skip to content

feat: Add Docker image for capiscio/guard sidecar#31

Merged
beonde merged 4 commits intomainfrom
feature/docker-guard
Jan 19, 2026
Merged

feat: Add Docker image for capiscio/guard sidecar#31
beonde merged 4 commits intomainfrom
feature/docker-guard

Conversation

@beonde
Copy link
Member

@beonde beonde commented Jan 18, 2026

Summary

Adds Docker support for the CapiscIO Guard sidecar, enabling easy deployment of the security gateway as a container.

Changes

  • Dockerfile: Multi-stage build using distroless for minimal, secure image (~15MB)
  • docker.yml: GitHub Action to build and push on release tags
    • Multi-arch support (linux/amd64, linux/arm64)
    • Auto-tagging (latest, vX.Y.Z, vX.Y, vX)
    • Verify job to test pushed images
  • Makefile: Added docker-build, docker-run, docker-push targets
  • README.md: Docker usage documentation with compose example

Usage

After merge and release:

# Pull the image
docker pull capiscio/guard

# Run in front of your agent
docker run -p 8080:8080 capiscio/guard \
  gateway start \
  --port 8080 \
  --target http://your-agent:3000 \
  --registry-url https://registry.capisc.io

Setup Required

Before the first release, add these secrets to the repository:

Secret Description
DOCKERHUB_USERNAME Docker Hub username with push access to capiscio org
DOCKERHUB_TOKEN Docker Hub access token (Read/Write/Delete)

Testing

# Build locally
make docker-build

# Run locally
make docker-run TARGET=http://localhost:3000

Related

This fulfills the marketing promise on capisc.io for docker pull capiscio/guard.

- 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
Copilot AI review requested due to automatic review settings January 18, 2026 21:02
@codecov
Copy link

codecov bot commented Jan 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 Dockerfile targeting 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.

Comment on lines +40 to +41
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +42
-ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \
-o /capiscio \
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@@ -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
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
.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

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +27
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
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +97
# 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
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
--target http://host.docker.internal:3000 \
--registry-url https://registry.capisc.io
```

Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
> **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).

Copilot uses AI. Check for mistakes.

steps:
- name: Checkout
uses: actions/checkout@v4
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

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'.

Suggested change
uses: actions/checkout@v4
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 19, 2026 01:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Comment on lines +40 to +42
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.version=${VERSION} -X main.commit=${COMMIT}" \
-o /capiscio \
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +53
// 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
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +184
# Test locally
make docker-run TARGET=http://localhost:3000
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
)

var version = "2.3.1"
var version = "2.4.0"
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +27
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
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@beonde beonde merged commit 5ce53b0 into main Jan 19, 2026
3 checks passed
@beonde beonde deleted the feature/docker-guard branch January 19, 2026 01:35
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