{"id":21928627,"url":"https://github.com/nuvious/debmirror-kubernetes","last_synced_at":"2026-05-03T16:33:51.301Z","repository":{"id":215680640,"uuid":"739543102","full_name":"nuvious/debmirror-kubernetes","owner":"nuvious","description":"A kubernetes deployment template for a self-hosted debian/ubuntu/apt mirror using kustomize.","archived":false,"fork":false,"pushed_at":"2024-09-02T05:47:51.000Z","size":9,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-30T19:46:44.585Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nuvious.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-05T20:28:40.000Z","updated_at":"2024-09-02T05:47:55.000Z","dependencies_parsed_at":"2024-11-28T22:27:59.562Z","dependency_job_id":"fdcda9a7-365a-40f3-84d6-7fc11d02c9af","html_url":"https://github.com/nuvious/debmirror-kubernetes","commit_stats":null,"previous_names":["nuvious/debmirror-kubernetes"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nuvious/debmirror-kubernetes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2Fdebmirror-kubernetes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2Fdebmirror-kubernetes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2Fdebmirror-kubernetes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2Fdebmirror-kubernetes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nuvious","download_url":"https://codeload.github.com/nuvious/debmirror-kubernetes/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nuvious%2Fdebmirror-kubernetes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32577122,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T06:36:36.687Z","status":"ssl_error","status_checked_at":"2026-05-03T06:36:09.306Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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-11-28T22:27:09.429Z","updated_at":"2026-05-03T16:33:51.277Z","avatar_url":"https://github.com/nuvious.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kubernetes Debian Mirror Template\n\nThis is a template deployment to self-host a debian mirror. Good for home labs, enterprises and everything in-between.\n\n## Requirements\n\n- A deployed kubernetes cluster and kubectl configured.\n  - This guide does not go through setting up a kubernetes cluster\n  - If you don't have kubernetes deployed but have a cluster and ansible skills, check out my [HomelabKubernetes repository](https://github.com/nuvious/HomelabKubernetes)\n  to deploy k3s or microk8s to your target nodes easily.\n- Some persistent storage resource such as an nfs, ceph, etc\n  - A debian mirror requires a lot of persistent storage\n  - Template uses hostPath and should be replaced with a proper network storage solution\n- Basic Kubernetes Knowledge\n  - While we will provide guides on patching the deployment we will not go into minute detail on individual resources\n  - Links are provided where appropriate in this guide to link out to resource documentation\n  - For documentation on different resources used in this project, check out [kubernetes documentation](https://kubernetes.io/docs)\n\n## Project Structure\n\nThis project follows the guidance outlined in [Declarative Management of Kubernetes Objects Using Kustomize](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/#bases-and-overlays)\nand uses bases and overlays to define deployments. Deployment-specific configurations should not be included in the\nbases, and overlays should be generated with patches to the base to cater to specific deployments. Below are the\nfiles in the project structure and their purpose:\n\n- base\n  - conf\n    - mirror.list\n      - A list of mirrors in [source.list syntax](https://wiki.debian.org/SourcesList) that defines the mirrors, distros\n        and components desired for mirroring.\n    - nginx.conf\n      - A definition for the persistent http service that will remain up between debmirror sync jobs.\n  - debmirror-mirror-job.yaml\n    - Job that periodically runs debmirror with the prescribed mirror list to update available packages.\n  - nginx.yaml\n    - The persistent http service.\n  - namespace.yaml\n    - A namespace resource; defaults to `debmirror`.\n  - service.yaml\n    - Defines the http service with the namespace.\n  - ingress.yaml\n    - Defines the ingress for the http service.\n  - kustomization.yaml\n    - The base kustomization configuration.\n\n## Quick Start\n\nWe will be creating an overlay for this specific deployment. In this overlay we can add deployment specific resources\nsuch as secrets or patching base resources. If you are unfamiliar with patching in a kustomize configuration using\noverlays, please review [patching documentation](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/#customizing)\nmaintained by kubernetes.\n\n### Create Initial Overlay Directory\n\nFirst we need to create an overlay. To do that simply make a directory like so:\n\n```bash\nmkdir -p overlays/local\n```\n\nReplace `local` with any meaningful directory name for your deployment.\n\n### Patch Persistent Storage\n\nThe [debmirror](https://linux.die.net/man/1/debmirror) tool used in the base container requires persistent storage;\n[as much as 100 gigs](https://help.ubuntu.com/community/Debmirror) if mirroring source and binaries. In this example\nI will be patching the [hostPath](https://kubernetes.io/docs/concepts/storage/volumes/#hostpath) volume type defined\nin the nginx deployment and the debmirror job. HostPath is the default one defined in the base as a placeholder but is\nonly okay to use if you're on a single-node kubernetes deployment. For multi-node an [nfs](https://kubernetes.io/docs/concepts/storage/volumes/#nfs),\n[ceph](https://kubernetes.io/docs/concepts/storage/volumes/#cephfs) or other network based storage should be used.\n\nFor this example I will be using a cephfs solution without a persistent volume claim but consult kubernetes\ndocumentation on [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) and the lifecycle\nof [Persistent Volume Claims](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#lifecycle-of-a-volume-and-claim)\nfor best-practices guidance. Since I'm using cephfs I will need to define a secret with the key used to authenticate\nwith ceph storage area network:\n\n```bash\ncat \u003e overlays/local/ceph-secret.yaml \u003c\u003c EOF\napiVersion: v1\nstringData:\n  key: [INSERT YOUR CEPHFS KEY]\nkind: Secret\nmetadata:\n  name: ceph-secret\nEOF\n```\n\nNow let's create the nginx deployment storage patch:\n\n```bash\ncat \u003e overlays/local/nginx-patch.yaml \u003c\u003c EOF\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: debmirror\n    component: http\n  name: debmirror-nginx\nspec:\n  template:\n    spec:\n      containers:\n      - name: debian-mirror-nginx\n        volumeMounts:\n        - name: data-patched\n          mountPath: /usr/share/nginx/html\n        - name: nginx-conf\n          mountPath: /etc/nginx/conf.d/default.conf\n          subPath: default.conf\n      volumes:\n      - name: mirror-list\n        configMap:\n          name: mirror-list\n      - name: nginx-conf\n        configMap:\n          name: nginx-conf\n      - name: data-patched\n        cephfs:\n          monitors:\n            - 192.168.1.70:6789\n            - 192.168.1.71:6789\n            - 192.168.1.72:6789\n          user: kube\n          secretRef:\n            name: ceph-secret\n          path: /kube/debmirror/data\nEOF\n```\n\nNext we'll modify the debmirror job to point to the same persistent storage. Note that instead of\n`[ROOT PERSISTENCE DIR]/data` we reference `[ROOT PERSISTENCE DIR]` without the `/data` subdirectory. That's because\nthe data directory is all that's needed to serve on the http service and the other directories are used by debmirror\nonly for keeping track of the state of the mirror.\n\nNow is also a good time to modify the\n[CronJob's schedule](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#writing-a-cronjob-spec)\nif desired.\n\n```bash\ncat \u003e overlays/local/debmirror-job-patch.yaml \u003c\u003c EOF\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: debmirror-cron\nspec:\n  schedule: \"0 3 * * 3\"\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          containers:\n          - name: debmirror-cron\n            volumeMounts:\n            - name: data-patched\n              mountPath: /var/spool/apt-mirror\n            - name: mirror-list-patched\n              mountPath: /etc/apt/mirror.list\n              subPath: mirror.list\n          volumes:\n          - name: mirror-list-patched\n            configMap:\n              name: mirror-list\n          - name: data-patched\n            cephfs:\n              monitors:\n                - 192.168.1.70:6789\n                - 192.168.1.71:6789\n                - 192.168.1.72:6789\n              user: kube\n              secretRef:\n                name: ceph-secret\n              path: /kube/debmirror # DO NOT USE /data SUBDIRECTORY HERE!\nEOF\n```\n\n### Modify Configurations and Ingress (Optional)\n\nWe will be replacing configmap resources defined in the base. This is only required if you want your hostname to be\nsomething besides `localhost` and/or if you want to pull updates from mirrors other than [debian.org](http://ftp.us.debian.org/debian/).\n\nFirst make the configuration directory:\n\n```bash\nmkdir -p overlays/local/conf\n```\n\nNext if it's desired to change the resolving hostname, create a `default.conf` with the desired hostname in\n[nginx configuration native syntax](https://nginx.org/en/docs/).\n\n```bash\ncat \u003e overlays/local/conf/default.conf \u003c\u003c EOF\nserver {\n    listen       80;\n    listen  [::]:80;\n    server_name  debmirror.local;\n\n    #access_log  /var/log/nginx/host.access.log  main;\n\n    location / {\n        root   /usr/share/nginx/html;\n        index  index.html index.htm;\n        autoindex on;\n    }\n\n    #error_page  404              /404.html;\n\n    # redirect server error pages to the static page /50x.html\n    #\n    error_page   500 502 503 504  /50x.html;\n    location = /50x.html {\n        root   /usr/share/nginx/html;\n    }\n}\nEOF\n```\n\nNext create a patch for the ingress to replace the hostname with the desired hostname:\n\n```bash\ncat \u003e overlays/local/ingress-patch.yaml \u003c\u003c EOF\n- op: replace\n  path: /spec/rules/0/host\n  value: debmirror.local\nEOF\n```\n\nFinally if it's desired to modify the mirror list, create a `mirror.list` replacement. I live close to Indiana so I\nchanged mine to the Purdue debian mirror. I also added `armhf` and `arm64` architectures to the mirror so I could update\nmy standard Debian deployments as well as my raspberry pi based deployments.\n\n```bash\ncat \u003e overlays/local/conf/mirror.list \u003c\u003c EOF\nset base_path         /var/spool/apt-mirror\nset mirror_path       $base_path/data\nset skel_path         $base_path/skel\nset var_path          $base_path/var\nset postmirror_script $var_path/postmirror.sh\nset defaultarch       amd64\nset run_postmirror    0\nset nthreads          6\nset limit_rate        100m\nset _tilde            0\nset unlink            1\nset use_proxy         off\nset http_proxy        127.0.0.1:3128\nset proxy_user        user\nset proxy_password    passwordx\ndeb http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware\ndeb [arch=armhf] http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware\ndeb [arch=arm64] http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware\ndeb-src http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware\ndeb http://security.debian.org/debian-security stable-security main contrib non-free-firmware\ndeb [arch=armhf] http://security.debian.org/debian-security stable-security main contrib non-free-firmware\ndeb [arch=arm64] http://security.debian.org/debian-security stable-security main contrib non-free-firmware\ndeb-src http://security.debian.org/debian-security stable-security main contrib non-free-firmware\ndeb http://plug-mirror.rcac.purdue.edu/debian stable-updates main\ndeb [arch=armhf] http://plug-mirror.rcac.purdue.edu/debian stable-updates main\ndeb [arch=arm64] http://plug-mirror.rcac.purdue.edu/debian stable-updates main\ndeb-src http://plug-mirror.rcac.purdue.edu/debian stable-updates main contrib non-free-firmware\ndeb http://plug-mirror.rcac.purdue.edu/debian stable-backports main\ndeb [arch=armhf] http://plug-mirror.rcac.purdue.edu/debian stable-backports main\ndeb [arch=arm64] http://plug-mirror.rcac.purdue.edu/debian stable-backports main\ndeb-src http://plug-mirror.rcac.purdue.edu/debian stable-backports main contrib non-free-firmware\ndeb [arch=armhf] http://plug-mirror.rcac.purdue.edu/raspbian stable main contrib non-free firmware rpi\nEOF\n```\n\n### Other Patches\n\nOne can also change the namespace deployed simply by modifying the namespace.yaml resource and/or modifying the\nkustomization.yaml. Changing the deployed namespace is not covered in this tutorial, but if desired, refer to the\n[kustomization documentation](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization) maintained\nby kubernetes for guidance on making these or other additional changes in the overlay.\n\n### Kustomize and Deploy\n\nAll that's left is to tie together all of the above with a `kustomization.yaml` file and deploy it. Below is the basic\ntemplate with optional line items highlighted with comments:\n\n```bash\ncat \u003e overlays/local/kustomization.yaml \u003c\u003c EOF\nnamespace: debmirror\nresources:\n  - ../../base\n  - ceph-secret.yaml\nconfigMapGenerator:\n  - name: mirror-list # Only necessary if you updated mirror.list\n    behavior: replace\n    files:\n      - conf/mirror.list\n  - name: nginx-conf # Only necessary if you overrode the default hostname localhost\n    behavior: replace\n    files:\n      - conf/default.conf\npatches:\n  - target:\n      kind: Ingress\n    path: ingress-patch.yaml # Only necessary if you overrode the default hostname localhost\npatchesStrategicMerge:\n  - debmirror-job-patch.yaml\n  - nginx-patch.yaml\nEOF\n```\n\nNow simply apply the overlay. The below command will also execute a watch on `kubectl get all -n debmirror` so you\ncan monitor the deployment:\n\n```bash\nkubectl apply -k overlays/local \u0026\u0026 \\\n    watch kubectl get all -n debmirror\n```\n\nThe output should resemble the following:\n\n```plaintext\nEvery 2.0s: kubectl get all -n debmirror     my-workstation: Fri Jan  5 14:17:23 2024\n\nNAME                                   READY   STATUS    RESTARTS   AGE\npod/debmirror-nginx-85ff7b64fc-jpcts   1/1     Running   0          46s\n\nNAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE\nservice/debmirror-service   ClusterIP   10.152.183.199   \u003cnone\u003e        80/TCP    47s\n\nNAME                              READY   UP-TO-DATE   AVAILABLE   AGE\ndeployment.apps/debmirror-nginx   1/1     1            1           47s\n\nNAME                                         DESIRED   CURRENT   READY   AGE\nreplicaset.apps/debmirror-nginx-85ff7b64fc   1         1         1       47s\n\nNAME                            SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE\ncronjob.batch/debmirror-cron   0 3 * * 3   False     0        \u003cnone\u003e          47s\n```\n\n### Force a Sync Job Execution\n\nInitiate a synchronization job manually with the following command:\n\n```bash\nkubectl create job -n debmirror  --from=cronjob/debmirror-cron debmirror-manual-job\n```\n\nYou can then monitor the job by watching the logs of the generated pod:\n\n```bash\n# Run kubectl get pods to get the name of you pod\nkubectl logs -f -n debmirror pods/debmirror-manual-job-86lbn \n```\n\nYou should see output similar to the below:\n\n```plaintext\nDownloading 318 index files using 6 threads...\nBegin time: Fri Jan  5 19:27:21 2024\n[6]... [5]... [4]... [3]... [2]... [1]... [0]... \nEnd time: Fri Jan  5 19:27:34 2024\n\nProcessing translation indexes: [TTTTTTTTTTTTT]\n...\n```\n\nWhen done, be sure to cleanup this resource with `kubectl delete job/debmirror-manual-job -n debmirror`.\n\nThe initial sync will take a long time and download 100's of GB's depending on your mirror list configuration. For me\nsince I was downloading x86-64, armhf and arm64, it was 380+ GB's. Follow on mirror runs will need far less. After your\ninitial sync job wait for the next scheduled sync job and you should see a much smaller size of download logged. Here's\nlogs from my first and most recent sync for comparison:\n\n```bash\n356.0 GiB will be downloaded into archive.\nDownloading 304327 archive files using 6 threads...\n...\n```\n\n```bash\n2.6 GiB will be downloaded into archive.\nDownloading 287 archive files using 6 threads...\n...\n```\n\nNotice the 2.6 GB vs several hundred. That shows your mirror is working and updating as expected. You can also\ninspect the completions with `kubectl get jobs -n debmirror`:\n\n```bash\n$\u003e kubectl get jobs -n debmirror\nNAME                       COMPLETIONS   DURATION   AGE\n20231228-apt-mirror-job    1/1           4h18m      8d\napt-mirror-cron-28404480   1/1           8m44s      2d11h\n```\n\nYou can use a browser or `curl` to inspect the file structure:\n\n```bash\n$\u003e curl debmirror.local\n\u003chtml\u003e\n\u003chead\u003e\u003ctitle\u003eIndex of /\u003c/title\u003e\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eIndex of /\u003c/h1\u003e\u003chr\u003e\u003cpre\u003e\u003ca href=\"../\"\u003e../\u003c/a\u003e\n\u003ca href=\"plug-mirror.rcac.purdue.edu/\"\u003eplug-mirror.rcac.purdue.edu/\u003c/a\u003e                       28-Dec-2023 17:14                   -\n\u003ca href=\"security.debian.org/\"\u003esecurity.debian.org/\u003c/a\u003e                               28-Dec-2023 21:03                   -\n\u003c/pre\u003e\u003chr\u003e\u003c/body\u003e\n\u003c/html\u003e\n$\u003e\n```\n\n### Update `sources.list`\n\nAfter your mirror is synced you should be able to point your package repository to the new mirror. Here's an example\n`/etc/apt/sources.list` I have from my debian template VM I used in my cluster:\n\n```plaintext\ndeb http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware\ndeb-src http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware\n\ndeb http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable-updates main contrib non-free-firmware\ndeb-src http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable-updates main contrib non-free-firmware\n```\n\nYour specific configuration will vary based on the mirrors you use, the distros/architectures you select and the\ncomponents you select. Also you can modify the paths in your nginx service and/or ingress as desired if you're only\nmirroring from a single mirror to exclude the hostname directory paths (ex make\n`deb http://debmirror..local/plug-mirror.rcac.purdue.edu/debian` just\n`deb http://debmirror..local/debian`).\n\nAfter updating, your `sources.list` you should be able to run `apt update` and see your server pull from your new local\nrepository:\n\n```bash\nnuvious@debiantemplate:~$ sudo apt update\nHit:1 http://debmirror.local/plug-mirror.rcac.purdue.edu/debian stable InRelease\nGet:2 http://debmirror.local/plug-mirror.rcac.purdue.edu/debian stable-updates InRelease [52.1 kB]\nFetched 52.1 kB in 0s (128 kB/s)\nReading package lists... Done\nBuilding dependency tree... Done\nReading state information... Done\nAll packages are up to date.\nnuvious@debiantemplate:~$\n```\n\n## Contributing\n\nAll contributions are welcome and MUST conform to the [Declarative Management of Kubernetes Objects Using Kustomize](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/)\ndocumentation.\n\nThe base image of this project will also persist as the one I maintain under `nuvious/apt-mirror-docker`. If you would\nlike to contribute to the image used in this deployment please check out\n[nuvious/apt-mirror-docker github repository](https://github.com/nuvious/apt-mirror-docker) and file issues/make\ncontributions there.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnuvious%2Fdebmirror-kubernetes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnuvious%2Fdebmirror-kubernetes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnuvious%2Fdebmirror-kubernetes/lists"}