Setting Up a Raspberry Pi as an Apt-Cacher-NG and Docker Registry Mirror for a Maker Shop Device Fleet
If you run more than one Raspberry Pi, a print farm controller, a few Klipper hosts, an OctoPrint box, a home server, and maybe a fleet managed with Ansible or balenaCloud, you have almost certainly noticed the same 200MB of Debian packages and the same handful of Docker base images getting downloaded over and over, once per device, every time you reimage or update. On a slow or metered internet connection this is wasted bandwidth and wasted time; during an actual internet outage it means a device you needed to rebuild right now simply cannot update. This guide sets up a single Raspberry Pi as a local caching layer for both APT packages and Docker/OCI images, so your whole shop's fleet pulls from a box on your own LAN instead of the public internet every time.
What This Actually Solves
- Bandwidth: The first device to request a package or image downloads it from upstream once; every other device on the LAN gets it from the cache at LAN speed.
- Resilience: Previously-cached packages and images remain available even if your internet connection is down, which matters if you are mid-rebuild on a Pi that just had its SD card die.
- Consistency: Every device in the fleet ends up pulling the same cached package versions during a given window, which reduces the "it works on this Pi but not that one" class of bug that comes from devices updating at slightly different times.
This pairs naturally with the site's Managing a Fleet of Raspberry Pis with Ansible and balenaCloud for Raspberry Pi Fleets guides — both become noticeably faster and more reliable once package and image pulls are hitting a local cache instead of the internet.
Hardware Requirements
A Raspberry Pi 4 or 5 with at least 4GB RAM and a decent amount of storage (a 128GB+ USB SSD strongly recommended over an SD card, both for cache size and for write endurance — see the site's Booting Raspberry Pi 5 from NVMe SSD guide if you want the fastest option) is sufficient to serve a fleet of a dozen or more devices. This does not need to be a dedicated machine; it can run alongside Pi-hole or other lightweight services on the same box if you already have one serving DNS for the shop.
Part 1: APT-Cacher-NG for Debian/Raspberry Pi OS Packages
Install and Configure
On the cache Pi:
sudo apt update sudo apt install apt-cacher-ng sudo systemctl enable --now apt-cacher-ngBy default it listens on port 3142 and stores its cache under /var/cache/apt-cacher-ng. Point it at a fast disk by editing /etc/apt-cacher-ng/acng.conf and setting CacheDir to a path on your USB SSD if you are not booting the whole OS from it.
Point Client Devices at the Cache
On every other Pi (and any Debian/Ubuntu box) in the fleet, create /etc/apt/apt.conf.d/02proxy with:
Acquire::http::Proxy "http://cache-pi-ip:3142";Run sudo apt update on a client and check /var/log/apt-cacher-ng/apt-cacher.log on the cache server — you should see the request logged. The first client to update a given package pulls it from upstream and caches it; every subsequent client pulls from the cache almost instantly.
Bake It Into New Images
For Raspberry Pi OS, add the same proxy config file to the boot partition's /etc/apt/apt.conf.d/ before first boot (or use a cloud-init/first-boot script) so every freshly flashed SD card is already pointed at the cache before it does its first update. If you are using DietPi or a custom image pipeline, the equivalent applies: bake the proxy pointer in at image-build time rather than configuring it manually after the fact.
Part 2: A Local Docker/OCI Registry Mirror
Docker Hub rate-limits anonymous pulls per IP, which becomes a real problem once you have several devices on the same public IP all pulling the same base images independently. A pull-through registry cache solves this the same way apt-cacher-ng does for packages.
Run the Registry Mirror Container
On the cache Pi (which needs Docker installed — see the site's Raspberry Pi Home Server guide for a Docker setup walkthrough):
docker run -d --restart=always --name registry-mirror \ -p 5000:5000 \ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \ -v /srv/registry-cache:/var/lib/registry \ registry:2This starts the official registry:2 image in pull-through cache mode: any image pulled through it is fetched from Docker Hub once and cached locally on subsequent requests.
Point Docker Clients at the Mirror
On every client device running Docker, edit (or create) /etc/docker/daemon.json:
{ "registry-mirrors": ["http://cache-pi-ip:5000"] }Restart Docker (sudo systemctl restart docker) and pull an image normally — docker pull nginx will now route through your mirror transparently, with no change needed to your Compose files or image names.
A Note on HTTPS
The registry mirror above runs plain HTTP, which is fine on a trusted internal LAN but requires adding the cache's address to Docker's insecure-registries list if your Docker version enforces TLS by default:
{ "registry-mirrors": ["http://cache-pi-ip:5000"], "insecure-registries": ["cache-pi-ip:5000"] }If your shop network is not fully trusted (shared makerspace, guest devices on the same VLAN), put this cache behind the same VLAN segmentation described in the site's VLAN Network Segmentation for the Maker Shop guide rather than exposing it broadly.
Verifying It's Actually Working
Watch the cache disk usage grow (du -sh /var/cache/apt-cacher-ng and du -sh /srv/registry-cache) after a batch of client updates, and time a second client's update against the first — the difference on a slow connection is dramatic, often the difference between minutes and seconds for a large image or a big package like a full ROS2 or TensorFlow install.
Maintenance
Neither cache prunes itself aggressively by default. apt-cacher-ng has a built-in expiration/cleanup job you can trigger from its web admin interface on port 3142; the registry mirror's cache can grow indefinitely and should be periodically checked or capped via a cron job that clears the cache volume if you are tight on storage. Neither cache needs backing up — everything in them is, by definition, re-downloadable from upstream if lost.
This is not a glamorous project, but it is the kind of infrastructure that quietly pays for itself the first time your internet goes down mid-rebuild, or the first time you provision five Pis in an afternoon instead of watching five progress bars crawl along one at a time on your actual internet connection.