{"id":16479330,"url":"https://github.com/timebertt/image-clone-controller","last_synced_at":"2026-06-08T18:31:42.201Z","repository":{"id":44646532,"uuid":"512880332","full_name":"timebertt/image-clone-controller","owner":"timebertt","description":null,"archived":false,"fork":false,"pushed_at":"2022-07-17T11:20:08.000Z","size":117,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-22T18:04:07.277Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/timebertt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-11T19:13:24.000Z","updated_at":"2023-03-05T09:12:33.000Z","dependencies_parsed_at":"2022-07-16T14:47:02.801Z","dependency_job_id":null,"html_url":"https://github.com/timebertt/image-clone-controller","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/timebertt/image-clone-controller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timebertt%2Fimage-clone-controller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timebertt%2Fimage-clone-controller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timebertt%2Fimage-clone-controller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timebertt%2Fimage-clone-controller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timebertt","download_url":"https://codeload.github.com/timebertt/image-clone-controller/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timebertt%2Fimage-clone-controller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34075954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T12:51:40.387Z","updated_at":"2026-06-08T18:31:42.181Z","avatar_url":"https://github.com/timebertt.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# image-clone-controller\n\n## Background\n\n### Goal\n\nWe'd like to be safe against the risk of public container images disappearing from the registry while we use them, breaking our deployments.\n\n### Problem\n\nWe have a Kubernetes cluster on which we can run applications. These applications will often use publicly available container images, like official images of popular programs, e.g. Jenkins, PostgreSQL, and so on. Since the images reside in repositories over which we have no control, it is possible that the owner of the repo deletes the image while our pods are configured to use it.\nIn the case of a subsequent node rotation, the locally cached copies of the images would be deleted and Kubernetes would be unable to re-download them in order to re-provision the applications.\n\n### Idea\n\nHave a controller which watches the applications and mirrors the images to our own registry repository and reconfigures the applications to use these copies.\n\n## Demo\n\n[![asciicast](https://asciinema.org/a/509182.svg)](https://asciinema.org/a/509182)\n\n[Play at asciinema.org](https://asciinema.org/a/509182)\n\n## Usage\n\n```bash\nmake kind-up # create local cluster including a local registry\nmake deploy # deploy the controller to kind\n```\n\nThe registry and controller should be up and running:\n```bash\n$ k -n registry get deploy,svc\nNAME                       READY   UP-TO-DATE   AVAILABLE   AGE\ndeployment.apps/registry   1/1     1            1           5m\n\nNAME               TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE\nservice/registry   NodePort   10.96.0.11   \u003cnone\u003e        5001:30501/TCP   5m\n\n$ k -n image-clone-system get deploy\nNAME                     READY   UP-TO-DATE   AVAILABLE   AGE\nimage-clone-controller   1/1     1            1           4m\n```\n\nNow, you're ready to deploy your workload!\n\n```bash\nk create deployment nginx --image nginx\nk create deployment grafana --image grafana/grafana:main\nk create deployment speedtest-exporter --image ghcr.io/timebertt/speedtest-exporter:v0.1.0\n```\n\n`image-clone-controller` will start copying your images to the local backup registry and update the `Deployment` objects to reference the copied image:\n```bash\n$ k get po -o=custom-columns=\"NAME:.metadata.name,IMAGE:.spec.containers[0].image\"\nNAME                                  IMAGE\ngrafana-6f844c97f6-9d9dn              10.96.0.11:5001/index_docker_io/grafana/grafana:main\nnginx-79cb9d6457-vrmpv                10.96.0.11:5001/index_docker_io/library/nginx:latest\nspeedtest-exporter-568df77fcd-mrftd   10.96.0.11:5001/ghcr_io/timebertt/speedtest-exporter:v0.1.0\n\n$ k get po\nNAME                                  READY   STATUS    RESTARTS   AGE\ngrafana-6f844c97f6-9d9dn              1/1     Running   0          3m\nnginx-79cb9d6457-vrmpv                1/1     Running   0          3m\nspeedtest-exporter-568df77fcd-mrftd   1/1     Running   0          3m\n```\n\nThe backup registry can be specified via the `--backup-registry` flag.\nImages are rewritten and copied to the backup registry using the following scheme:\n```text\n# docker library images\nnginx                                        -\u003e \u003cdstRegistry\u003e/index_docker_io/library/nginx:latest\n# tag is kept\nnginx:1.23                                   -\u003e \u003cdstRegistry\u003e/index_docker_io/library/nginx:1.23\n# digest is rewritten to tag\nnginx@sha256:33cef...                        -\u003e \u003cdstRegistry\u003e/index_docker_io/library/nginx:sha256_33cef...\n# non-library images from Docker Hub\ngrafana/grafana:main                         -\u003e \u003cdstRegistry\u003e/index_docker_io/grafana/grafana:main\n# other registries\nghcr.io/timebertt/speedtest-exporter:v0.1.0  -\u003e \u003cdstRegistry\u003e/ghcr_io/timebertt/speedtest-exporter:v0.1.0\n```\n\n## Development\n\nThe controller is scaffolded with [kubebuilder](https://book.kubebuilder.io/) and implemented using [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime).\nHowever, for the sake of simplicity, the default kubebuilder manifest structure has been trimmed down significantly.\n\n\nYou can easily develop this controller using [skaffold](https://skaffold.dev):\n\n```bash\n# create local cluster including a local registry\nmake kind-up\n\n# use skaffold to build a fresh image and deploy to kind\nmake up # one-time build and deployment\nmake dev # dev loop with re-build an deployment on trigger\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimebertt%2Fimage-clone-controller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimebertt%2Fimage-clone-controller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimebertt%2Fimage-clone-controller/lists"}