Container build definitions and CI/CD pipelines for AxonOps container images.
- Components
- Red Hat Universal Base Image (UBI)
- Repository Conventions
- Security
- Development
- Releasing
- Acknowledgements
- License
- Legal Notices
- k8ssandra/ - Apache Cassandra with AxonOps integration for K8ssandra Operator
- axonops/ - AxonOps self-hosted stack (complete platform components)
- axonops-schema-registry/ - Confluent-compatible Schema Registry with multi-backend storage support
All containers in this repository are built on Red Hat Universal Base Image (UBI) 9, providing enterprise-grade security, stability, and compliance.
Why Red Hat UBI?
- Freely Redistributable - No subscription required to use or redistribute
- Enterprise Security - Regular security updates and CVE patches from Red Hat
- Production Hardened - Minimal attack surface with only essential packages
- Compliance Ready - Meets requirements for regulated industries (finance, healthcare, government)
- Long-Term Support - Stable base with predictable lifecycle (RHEL 9 supported until 2032)
- Container Optimized - Purpose-built for containerized workloads with minimal footprint
Learn More:
- Multi-architecture support: linux/amd64, linux/arm64
- Published to: GitHub Container Registry
ghcr.io/axonops/<image-name>:<tag> - Automated CI/CD: GitHub Actions with comprehensive testing
- Security scanning: Trivy vulnerability scanning on all images
- Base images: Red Hat UBI 9 (digest-pinned for supply chain security)
Immutable Tagging: All container images use immutable versioning. When CVEs are discovered and patched, we release NEW versions rather than overwriting existing tags.
Version Increments:
- Critical CVEs (CRITICAL, HIGH severity): Immediate patch release (e.g.,
1.0.3→1.0.4) - Non-critical CVEs (MEDIUM, LOW): Batched into monthly releases
- Patch version increments may include: CVE fixes, component updates (e.g., AxonOps agent), bug fixes, or feature additions
Latest Tag Behavior:
latesttag always points to the most recent secure version- Provides automatic security updates when using latest tag
- NOT recommended for production - use specific versions instead
Production Deployment:
- Always pin to specific immutable versions (e.g.,
5.0.6-v0.1.110-1.0.5) - Never use
latest,5.0-latest, or{version}-latesttags in production - Review release notes before upgrading
- Test upgrades in non-production environments first
CVE Notifications:
- Automated nightly security scans with Trivy
- Email notifications for new CRITICAL/HIGH CVEs
- Transparent disclosure in release notes
For highest security environments, see Gold Standard Security Deployment.
Digest-Based Deployment provides the highest level of security and immutability for container deployments.
Instead of using tags (which can be mutable), deploy using the image's SHA256 digest:
# Tag-based (good)
image: ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# Digest-based (best)
image: ghcr.io/axonops/k8ssandra/cassandra@sha256:412c85225...✅ 100% Immutable - Guaranteed exact same image every time, forever
✅ Survives Tag Manipulation - Unaffected if tags are accidentally or maliciously changed
✅ Compliance Ready - Meets requirements for regulated environments (finance, healthcare, government)
✅ Audit Trail - Digest in deployment manifest provides cryptographic proof of exact image
✅ Supply Chain Security - Combined with signature verification, provides complete provenance
Method 1: From GHCR UI
- Navigate to package: https://github.com/axonops/axonops-containers/pkgs/container/k8ssandra%2Fcassandra
- Click on specific version
- Copy SHA256 digest shown
Method 2: Using Docker/Podman
# Pull the image first
docker pull ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# Get digest
docker inspect ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5 \
--format='{{index .RepoDigests 0}}'
# Output: ghcr.io/axonops/k8ssandra/cassandra@sha256:412c85225...Method 3: During Workflow
- Check GitHub Actions workflow logs after publishing
- Digest printed in build output
K8ssandraCluster manifest:
apiVersion: k8ssandra.io/v1alpha1
kind: K8ssandraCluster
metadata:
name: production-cluster
spec:
cassandra:
serverVersion: "5.0.6"
# Use digest instead of tag
serverImage: "ghcr.io/axonops/k8ssandra/cassandra@sha256:412c852252ec4ebcb8d377a505881828a7f6a5f9dc725cc4f20fda2a1bcb3494"
datacenters:
- metadata:
name: dc1
size: 3
# ... rest of configurationAll images published to GHCR are signed with Sigstore Cosign using keyless signing.
Standard Verification:
# Install cosign
brew install sigstore/tap/cosign # macOS
# or: https://docs.sigstore.dev/cosign/installation/
# Verify signature
cosign verify \
--certificate-identity-regexp='https://github.com/axonops/axonops-containers' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# Check signature exists
cosign tree ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5Troubleshooting (macOS Issues):
If you encounter issues with local cosign on macOS, use the official Cosign container image:
# Using Docker
docker run --rm gcr.io/projectsigstore/cosign:v2.4.1 verify \
--certificate-identity-regexp='https://github.com/axonops/axonops-containers' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# Using Podman
podman run --rm gcr.io/projectsigstore/cosign:v2.4.1 verify \
--certificate-identity-regexp='https://github.com/axonops/axonops-containers' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5This uses the official Cosign container and works reliably across all platforms.
Successful verification proves:
- ✅ Image was built by official GitHub Actions workflow
- ✅ Image has not been tampered with
- ✅ Build provenance is traceable to specific commit and workflow run
For production environments requiring signature verification before deployment:
Policy Enforcement Tools:
- Kyverno - Kubernetes native policy engine
- OPA Gatekeeper - Open Policy Agent for Kubernetes
- Sigstore Policy Controller - Official Sigstore admission controller
Example: Kyverno Policy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-axonops-images
spec:
validationFailureAction: Enforce
rules:
# RULE 1: Deny any image NOT from ghcr.io/axonops/
- name: check-registry
match:
any:
- resources:
kinds: [Pod]
namespaces: [kafka,k8ssandra-operator,strimzi]
validate:
message: "Only images from ghcr.io/axonops/ are allowed in this namespace."
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
all:
- key: "{{ element.image }}"
operator: NotEquals # Use NotEquals with wildcards
value: "ghcr.io/axonops/*"
# RULE 2: Verify the signatures of those images
- name: verify-signature
match:
any:
- resources:
kinds: [Pod]
namespaces: [kafka,k8ssandra-operator,strimzi]
verifyImages:
- imageReferences:
- "ghcr.io/axonops/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/axonops/axonops-containers/*"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.devCloud Provider Support:
- AWS EKS - Use Kyverno or OPA Gatekeeper via Helm
- Google GKE - Binary Authorization with Cosign attestations
- Azure AKS - Azure Policy with Ratify + Cosign
- Rancher/RKE - Kyverno or OPA Gatekeeper via Rancher Apps
For detailed setup, see your Kubernetes distribution's documentation on admission controllers and image policy enforcement.
For Production Clusters:
- ✅ Use digest-based deployment
- ✅ Verify signatures before deployment (signed images)
- ✅ Pin digest in version control (GitOps)
- ✅ Document digest → version mapping in release notes
- ✅ Update digests only after testing in non-production
For Development/Testing:
- Tag-based deployment is acceptable for faster iteration
- Use development image registry for testing
Updating Digests:
# 1. Pull new version
docker pull ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# 2. Get new digest
NEW_DIGEST=$(docker inspect ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5 \
--format='{{index .RepoDigests 0}}' | cut -d@ -f2)
# 3. Update manifest
sed -i "s|@sha256:.*|@${NEW_DIGEST}\"|" k8ssandra-cluster.yaml
# 4. Review, test, and commit
git diff k8ssandra-cluster.yamlBranch Structure:
development- Default branch, commit directly heremain- Production releases only (PR required)feature/*- Optional, for complex features
See DEVELOPMENT.md for complete guidelines.
Developer Workflow:
# Work directly on development
git checkout development && git pull origin development
git add . && git commit -S -m "Add feature" && git push origin development
# For production: create PR development → main (approval required)Every container we build MUST follow these security practices:
-
Digest Pinning (Supply Chain Security)
- ALWAYS pin base images by SHA256 digest, NEVER by tag
- Tags are mutable - can be replaced maliciously
- Digests are immutable - cryptographically guaranteed
- Example:
# CORRECT FROM upstream/image@sha256:abc123... # WRONG - Supply chain vulnerability! FROM upstream/image:latest FROM upstream/image:v1.0.0
-
Container Signing (Authenticity)
- ALL published images MUST be signed with Cosign
- Use keyless signing with GitHub OIDC (no secret management)
- Sign by digest immediately after build
- Publish to
ghcr.io/axonops/<component>/<image-name>:tag
-
Verification & Testing
- Verify checksums for downloaded artifacts (RPMs, tarballs, etc.)
- Verify base image digest matches expected version
- Automated startup error detection
- Security scanning with Trivy before publishing
Why these standards matter:
- Prevents supply chain attacks (malicious base images)
- Ensures image authenticity (Cosign signatures)
- Provides full audit trail (digests + signatures)
- Meets compliance requirements for regulated environments
Purpose: Publish signed images to development registry for testing before production release.
Registry: ghcr.io/axonops/development/<component>/<image-name>
Example: ghcr.io/axonops/development/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.0
Characteristics:
- All images are Cosign signed (same as production)
- Uses same security standards as production (digest pinning, checksums, verification)
- Allows testing with specific versions
- No GitHub Releases created
- Tagged from
developmentbranch
Process:
# 1. Tag development branch (any name, e.g., dev-feature-x, dev-1.0.0)
git checkout development && git pull origin development
git tag dev-1.0.0 && git push origin dev-1.0.0
# 2. Trigger development publish workflow
gh workflow run development-<component>-publish.yml \
-f dev_git_tag=dev-1.0.0 \
-f container_version=dev-1.0.0
# 3. Test development image
docker pull ghcr.io/axonops/development-<image>:5.0.6-dev-1.0.0
# Run tests, validate functionality
# 4. Promote to main (auto-creates PR)
git tag merge-1.0.0 && git push origin merge-1.0.0
# PR auto-created, review and merge to mainUse for: Feature testing, integration testing, QA validation before production.
Purpose: Publish stable, tested images to production registry.
Registry: ghcr.io/axonops/<image-name>
Characteristics:
- Immutable (version validation prevents overwrites)
- Creates GitHub Releases
- Tagged from
mainbranch only - Only after testing in development
Prerequisites:
- Changes merged to
developmentand tested - Development images validated (if published)
- PR from
development→mainapproved and merged
Process:
Step 1: Create Git Tag on Main Branch
IMPORTANT: Tags must be created on the main branch. The publish workflow will validate this.
# Ensure you're on main and up to date
git checkout main
git pull origin main
# Tag the commit
git tag 1.0.0
# Push tag to remote
git push origin 1.0.0The tag can be any name (e.g., 1.0.0, v1.0.0, release-2024-12). It marks the exact code snapshot to build from.
Note: If you tag a commit not on main, the publish workflow will fail with an error.
Step 2: Trigger Publish Workflow
You can trigger the publish workflow via GitHub CLI or GitHub UI.
Install and authenticate (first time only):
# macOS
brew install gh
# Linux
# See: https://github.com/cli/cli#installation
# Authenticate
gh auth loginTrigger the signed publish workflow:
gh workflow run <component>-publish-signed.yml \
-f main_git_tag=1.0.0 \
-f container_version=1.0.0Arguments explained:
-f main_git_tag=1.0.0- The git tag on main branch to checkout and build (the tag you created in Step 1)-f container_version=1.0.0- The container version for GHCR images (e.g., creates5.0.6-v0.1.110-1.0.5)
Note: Use the -signed workflows (k8ssandra-publish-signed.yml) for new releases. These publish to the new image paths with cryptographic signatures. Old workflows remain for backward compatibility but are deprecated.
Monitor progress:
gh run watch- Navigate to Actions tab in GitHub repository
- Select the publish workflow (e.g., K8ssandra Publish to GHCR)
- Click Run workflow button (top right)
- A form appears with inputs:
- main_git_tag: Enter the git tag on main branch created in Step 1 (e.g.,
1.0.0)- This determines which code to build
- container_version: Enter the container version (e.g.,
1.0.0)- This becomes the container version on published images
- Example:
5.0.6-v0.1.110-1.0.5where1.0.0is the container version
- main_git_tag: Enter the git tag on main branch created in Step 1 (e.g.,
- Click Run workflow to start
Step 3: Workflow Execution
The signed publish workflow will:
- Validate tag is on main branch (fails if not)
- Validate
container_versiondoesn't exist in GHCR (fails if duplicate) - Checkout the
main_git_tagcommit (exact code snapshot) - Run full test suite
- Build multi-arch images (amd64, arm64)
- Push to GHCR with multi-dimensional tags
- Sign images with Sigstore Cosign (keyless, GitHub OIDC)
- Re-push tags to ensure proper GHCR UI display
- Create GitHub Release named
<component>-signed-<container_version>
Images are signed using keyless signing with transparency log entries. Signatures can be verified with cosign verify (see Verifying Signatures).
Step 4: Verify Release
# View GitHub Release
gh release view k8ssandra-signed-1.0.0
# Pull and test image
docker pull ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# Verify signature
cosign verify \
--certificate-identity-regexp='https://github.com/axonops/axonops-containers' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5
# Or check signature exists
cosign tree ghcr.io/axonops/k8ssandra/cassandra:5.0.6-v0.1.110-1.0.5All production images are cryptographically signed. Signature verification proves the image was built by official workflows and has not been tampered with. See Gold Standard Security Deployment for more details.
Each component has detailed release documentation:
We extend our appreciation to the Apache Cassandra community for their outstanding work and contributions to the distributed database field. Apache Cassandra is a free and open-source, distributed, wide-column store, NoSQL database management system designed to handle large amounts of data across many commodity servers, providing high availability with no single point of failure.
For more information:
We acknowledge K8ssandra for providing excellent Kubernetes operator and management tools for Apache Cassandra. K8ssandra is a production-ready platform for running Apache Cassandra on Kubernetes, including backup/restore, repairs, and monitoring capabilities.
For more information:
We acknowledge AxonOps Schema Registry for providing a Confluent-compatible Schema Registry with support for multiple storage backends including PostgreSQL, MySQL, and Apache Cassandra.
For more information:
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
This project may contain trademarks or logos for projects, products, or services. Any use of third-party trademarks or logos are subject to those third-party's policies.
- AxonOps is a registered trademark of AxonOps Limited
- Apache, Apache Cassandra, and Cassandra are trademarks of the Apache Software Foundation or its subsidiaries in Canada, the United States and/or other countries
- Apache Kafka and Kafka are trademarks of the Apache Software Foundation
- K8ssandra is a trademark of the Apache Software Foundation
- Docker is a trademark or registered trademark of Docker, Inc. in the United States and/or other countries
- Podman is a trademark of Red Hat, Inc.
- OpenSearch is a trademark of Amazon.com, Inc. or its affiliates
- Kubernetes is a registered trademark of The Linux Foundation
Made with ❤️ by AxonOps