Run SyncerD on Kubernetes as a CronJob. Each run executes syncerd sync --once; the schedule is controlled by the CronJob (default: every 3 weeks). Stateless by default (no PVC): SyncerD checks each destination registry and skips tags that already exist. Set persistence.enabled: true to persist state across runs for faster "already synced" checks.
- Kubernetes 1.19+
- Helm 3+
- (Optional) PVC provisioner for state persistence
# From the syncerd repo root
helm install syncerd ./_helm/syncerd -n syncerd --create-namespace
# With custom values
helm install syncerd ./_helm/syncerd -n syncerd --create-namespace -f my-values.yaml| Key | Description | Default |
|---|---|---|
image.repository |
SyncerD image | ghcr.io/clouddrove/syncerd |
image.tag |
Image tag | latest |
imagePullSecrets |
Secrets to pull the SyncerD image itself (e.g. from a private registry) | [] |
cronjob.enabled |
Run the image sync CronJob. Set false for a deployment that only wants git mirroring |
true |
cronjob.schedule |
Cron schedule (K8s CronJob) | 0 0 */21 * * (every 3 weeks) |
persistence.enabled |
Use PVC for state file; stateless when false | false |
persistence.size |
PVC size when persistence enabled | 1Gi |
config.source |
Docker Hub source config | see values.yaml |
config.destinations |
List of destination registries | [] |
config.images |
Images to sync | [] |
config.state_path |
State file path (only used when persistence.enabled: true) |
/data/.syncerd-state.json |
config.slack |
Slack notification settings | see values.yaml |
existingSecret |
Name of existing Secret for Docker Hub / Slack credentials | "" |
secret.dockerhubUsername / dockerhubPassword / dockerhubToken |
Inline Docker Hub creds (use existingSecret in production) |
"" |
secret.slackWebhookUrl / slackChannel |
Inline Slack settings | "" |
dockerConfigSecret |
Secret containing .dockerconfigjson for destination registry auth (ECR, ACR, GCR, GHCR) |
"" |
resources |
CPU/memory limits for the job | see values.yaml |
Use existingSecret (recommended for production):
kubectl create secret generic syncerd-creds -n syncerd \
--from-literal=DOCKERHUB_USERNAME=myuser \
--from-literal=DOCKERHUB_PASSWORD=mytokenexistingSecret: syncerd-credsOr for Slack notifications, add to the same secret:
kubectl create secret generic syncerd-creds -n syncerd \
--from-literal=DOCKERHUB_USERNAME=myuser \
--from-literal=DOCKERHUB_PASSWORD=mytoken \
--from-literal=SYNCERD_SLACK_WEBHOOK_URL=https://hooks.slack.com/...SyncerD uses the Docker credential config for destination registry auth. Create a secret from your local Docker config after logging in, then set dockerConfigSecret.
DOCKER_CONFIG=/var/lib/syncerd/.docker is set automatically when dockerConfigSecret is configured. The secret is mounted at that path (not under /root) so the credentials are readable by the non-root pod user.
Note:
dockerConfigSecretis only for destination registry auth. To pull the SyncerD image itself from a private registry, useimagePullSecretsinstead — these are separate concerns.
Give the pod AWS credentials (IRSA, EKS Pod Identity, or a node instance role) and skip dockerConfigSecret entirely: SyncerD requests an ECR token itself through the standard AWS credential chain and refreshes it as it expires. The role needs ecr:GetAuthorizationToken plus push permissions on the destination repositories.
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/syncerdWithout AWS credentials, a Docker config secret still works, but ECR tokens expire every 12 hours, so the secret has to be refreshed before each sync window.
# Log in and create/update the secret
aws ecr get-login-password --region <region> | \
docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com
kubectl create secret generic syncerd-docker-config \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockerconfigjson \
-n syncerd --dry-run=client -o yaml | kubectl apply -f -dockerConfigSecret: syncerd-docker-configdocker login myregistry.azurecr.io \
--username <client-id> --password <client-secret>
kubectl create secret generic syncerd-docker-config \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockerconfigjson \
-n syncerd --dry-run=client -o yaml | kubectl apply -f -gcloud auth configure-docker
kubectl create secret generic syncerd-docker-config \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockerconfigjson \
-n syncerd --dry-run=client -o yaml | kubectl apply -f -echo $GITHUB_TOKEN | docker login ghcr.io \
--username <github-username> --password-stdin
kubectl create secret generic syncerd-docker-config \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockerconfigjson \
-n syncerd --dry-run=client -o yaml | kubectl apply -f -docker login accumulates credentials in ~/.docker/config.json, so log in to all registries before creating the secret:
# Log in to all destinations
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
docker login myregistry.azurecr.io --username <id> --password <secret>
# Create one secret covering all of them
kubectl create secret generic syncerd-docker-config \
--from-file=.dockerconfigjson=$HOME/.docker/config.json \
--type=kubernetes.io/dockerconfigjson \
-n syncerd --dry-run=client -o yaml | kubectl apply -f -The chart can also run syncerd git-sync as a second, independent CronJob, so it can keep its own schedule apart from image sync. Disabled by default. All five provider types are supported: GitHub, GitLab, Bitbucket, Azure DevOps, and AWS CodeCommit, and any of them can be a source or a destination.
| Key | Description | Default |
|---|---|---|
gitSync.enabled |
Run the git mirroring CronJob | false |
gitSync.schedule |
Cron schedule for the gitsync CronJob | 0 */6 * * * |
gitSync.persistence.enabled |
Use a PVC for the clone cache and mirror state; emptyDir when false (one-shot testing only, see below) |
true |
gitSync.persistence.size |
PVC size when persistence enabled | 10Gi |
gitSync.workDir |
Clone cache path inside the container | /gitdata/cache |
gitSync.statePath |
Mirror state file path inside the container | /gitdata/cache/git-state.json |
gitSync.concurrency |
Parallel repository mirrors per run | 4 |
gitSync.providers |
List of git providers (name, type, owner, ...); tokens are never set here |
[] |
gitSync.mirrors |
List of mirrors (name, source, destination, push_mode, ...) |
[] |
gitSync.resources |
CPU/memory limits for the gitsync job | see values.yaml |
cronjob.enabled and gitSync.enabled are independent: set cronjob.enabled: false and gitSync.enabled: true for a deployment that only mirrors git repositories.
gitSync.workDir (the clone cache) and gitSync.statePath (the ref-fingerprint state) both live under /gitdata, backed by the same PVC. Losing state while keeping the cache makes every repository fail the adopt guard on the next run, because SyncerD no longer knows it populated those destinations itself. Keeping both on one volume means a lost volume loses both together, which is a clean first-run state, not a partial one.
Provider tokens and git credentials are never written to the ConfigMap. They are read from a Secret as environment variables named SYNCERD_GIT_<KEY>_TOKEN, SYNCERD_GIT_<KEY>_GIT_USERNAME, and SYNCERD_GIT_<KEY>_GIT_PASSWORD, where <KEY> is derived from the provider's name in gitSync.providers: upper cased, with every non-alphanumeric character replaced by underscore. A provider named gh reads SYNCERD_GIT_GH_TOKEN; a provider named gh-mirrors reads SYNCERD_GIT_GH_MIRRORS_TOKEN.
| Provider | Secret key(s) it reads |
|---|---|
| GitHub | SYNCERD_GIT_<KEY>_TOKEN (PAT) |
| GitLab | SYNCERD_GIT_<KEY>_TOKEN (group access token) |
| Bitbucket | SYNCERD_GIT_<KEY>_TOKEN (API token; the username is the email set in gitSync.providers, not a secret) |
| Azure DevOps | SYNCERD_GIT_<KEY>_TOKEN (org-scoped PAT, or an operator supplied Entra ID access token when auth: entra) |
| AWS CodeCommit | SYNCERD_GIT_<KEY>_GIT_USERNAME and SYNCERD_GIT_<KEY>_GIT_PASSWORD (static IAM HTTPS Git credentials for the git transport); the API half authenticates via IRSA, an instance role, or the standard AWS credential chain, not a secret here |
Set them with existingSecret (recommended) or inline via secret.gitTokens / secret.gitCredentials:
kubectl create secret generic syncerd-creds -n syncerd \
--from-literal=SYNCERD_GIT_GH_TOKEN=ghp_your_token \
--from-literal=SYNCERD_GIT_GL_TOKEN=glpat-your_tokenexistingSecret: syncerd-credsOr inline (not recommended for production):
secret:
gitTokens:
gh: "ghp_your_token"
gl: "glpat-your_token"
# gitCredentials keys map to SYNCERD_GIT_<KEY>_GIT_USERNAME / _GIT_PASSWORD.
# This is CodeCommit's intended git transport credential, not a future or
# optional one: SyncerD does not derive SigV4 git credentials, so a
# static IAM HTTPS Git credential pair is how a CodeCommit provider
# authenticates git push and clone, even when the API half authenticates
# via IRSA or an instance role.
# gitCredentials:
# cc:
# username: "..."
# password: "..."- CodeCommit: SyncerD does not derive SigV4 git credentials, so IRSA and instance roles cover listing and creating repositories but the git transport needs static IAM HTTPS Git credentials, set via
secret.gitCredentialsorexistingSecret. - Azure DevOps Entra mode: the operator supplies the access token; SyncerD does not acquire one from Azure AD. Set
auth: entraon the provider and supply the token through the sameSYNCERD_GIT_<KEY>_TOKENkey used for a PAT. - Bitbucket: Cloud only. The
api_urloverride changes the host but the request paths are Cloud shaped, so Bitbucket Data Center is not supported. Bitbucket also has no archived concept, soskip_archivedhas no effect for a Bitbucket source.
cronjob:
enabled: false
gitSync:
enabled: true
providers:
- name: gh
type: github
owner: my-org
- name: gl
type: gitlab
owner: my-group
mirrors:
- name: gh-to-gl
source: gh
destination: gl
create_missing: true
existingSecret: syncerd-credskubectl create job --from=cronjob/<release-name> syncerd-manual-$(date +%s) -n syncerdhelm uninstall syncerd -n syncerd
# Optionally delete PVC: kubectl delete pvc -l app.kubernetes.io/name=syncerd -n syncerd