Skip to content
jarodr47Public

About

Portage is a Kubernetes operator that declaratively syncs container images between registries. The name comes from the act of carrying cargo between two bodies of water, which is exactly what this does: carrying container images between two registries.

Resources

Stars

55 stars

Watchers

0 watching

Forks

Repository files navigation

Portager Logo

Portager

Kubernetes operator for declarative registry-to-registry image sync

Quick Start • Configuration • Deploy Guide • Metrics • Buy Me a Coffee


Portager is a Kubernetes operator that declaratively syncs container images between OCI-compliant registries. Define an ImageSync custom resource specifying source images, a destination registry, a cron schedule, and authentication — the operator handles the rest.

The name comes from the act of carrying cargo between two bodies of water, which is exactly what this does: carrying container images between two registries.

How It Works

You create an ImageSync CR. The operator compares source and destination digests on your cron schedule, copies only what's changed, and reports status back on the resource. No pipelines, no scripts — just a CRD and a controller.

                    ┌──────────────┐
                    │  ImageSync   │
                    │     CRD      │
                    └──────┬───────┘
                           │
                           ▼
  ┌─────────┐     ┌────────────────┐     ┌─────────┐
  │  Source │────▶│   Portager     │────▶│  Dest   │
  │ Registry│     │  Controller    │     │ Registry│
  └─────────┘     └────────────────┘     └─────────┘
   Docker Hub      Digest compare         ECR
   GHCR            Copy if changed        GCR
   Chainguard      Skip if matching       Harbor
   Quay            Update status          Nexus

Why Portager

There is no CRD-native operator in the Kubernetes ecosystem for declarative registry-to-registry image relocation. The existing alternatives each fall short:

Alternative Limitation
ECR pull-through cache AWS-only, no support for arbitrary private registries (e.g., Chainguard cgr.dev), unavailable in GovCloud
dregsy Config-file driven, not CRD-driven — no kubectl get imagesync
kube-image-keeper (kuik) Caches to an in-cluster registry only; does not push to external registries
Flux image automation Watches for new tags and updates Git manifests; does not copy images
CI/CD pipelines Couples relocation to CI availability; no Kubernetes-native status reporting

Portager fills this gap with a Kubernetes-native, GitOps-friendly approach that works with any OCI-compliant registry.

Features

  • Declarative — Define image sync rules as Kubernetes custom resources
  • Digest-based skip — Compares manifest digests via HTTP HEAD; skips unchanged images without downloading layers
  • Registry-agnostic — Works with Docker Hub, GHCR, Quay, Chainguard, ECR, GCR, Harbor, Nexus, and any OCI-compliant registry
  • Pluggable auth — Kubernetes Secrets (dockerconfigjson), ECR via IRSA, or anonymous for public registries
  • Cron scheduling — Standard cron expressions, shorthands like @every 6h, and on-demand sync via annotation
  • Semver tag filtering — Auto-discover tags matching semver constraints like 1.x, ~1.22.0, or ^22.0.0 instead of listing every tag
  • Pre-sync validation — Optional cosign signature verification, vulnerability severity gating (SARIF), and SBOM existence checks before copying
  • Private PKI — Trust registries signed by an internal CA by mounting a bundle from a Secret or ConfigMap, with an air-gapped mode that trusts only your CA
  • Observable — Per-image status on the resource, Kubernetes Events, and custom Prometheus metrics

Installation

Prerequisites

  • Kubernetes 1.28+
  • Helm v3+

Install with Helm

helm install portager oci://ghcr.io/jarodr47/portager/charts/portager \
  --version 0.5.0 -n portager-system --create-namespace
Install with Kustomize
git clone https://github.com/jarodr47/portager.git
cd portager
make install   # Install CRDs
make deploy    # Deploy controller + RBAC

Verify

kubectl get pods -n portager-system
# NAME                                           READY   STATUS    AGE
# portager-controller-manager-xxxxxxxxxx-xxxxx   1/1     Running   30s

Quick Start

# base-images.yaml
apiVersion: portager.portager.io/v1alpha1
kind: ImageSync
metadata:
  name: base-images
  namespace: default
spec:
  schedule: "@every 6h"
  source:
    registry: docker.io/library
  destination:
    registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
    auth:
      method: ecr
    repositoryPrefix: mirror
  createDestinationRepos: true
  images:
    - name: alpine
      tags: ["3.21", "latest"]
    - name: nginx
      tags: ["latest"]
      semver: "~1.27.0"          # auto-sync newest 1.27.x patch releases
kubectl apply -f base-images.yaml
kubectl describe imagesync base-images
Events:
  Type    Reason        Message
  ----    ------        -------
  Normal  RepoEnsured   ECR repository "mirror/alpine" exists or was created
  Normal  ImageSynced   Synced docker.io/library/alpine:3.21 -> ECR (digest: sha256:c3f8e73f)
  Normal  SyncComplete  Sync complete: 4 synced, 0 failed, 4 total

For full deployment walkthroughs — including EKS with IRSA, non-EKS clusters, and private source registries — see the Deploy Guide.

For Helm values, ImageSync spec reference, auth strategies, and metrics, see Configuration.

Uninstalling

kubectl delete imagesync --all -A
helm uninstall portager -n portager-system
kubectl delete crd imagesyncs.portager.portager.io
kubectl delete ns portager-system
Uninstall with Kustomize
kubectl delete imagesync --all -A
make undeploy
make uninstall

Contributing

make build          # Build the controller binary
make test           # Run unit and integration tests
make lint           # Run golangci-lint
make helm-lint      # Lint the Helm chart

See Configuration — Development for the full local development setup.

License

Licensed under the Apache License, Version 2.0. See LICENSE for details.

About

Portage is a Kubernetes operator that declaratively syncs container images between registries. The name comes from the act of carrying cargo between two bodies of water, which is exactly what this does: carrying container images between two registries.

Resources

Stars

55 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages