Mirror, mirror on the wall, apt-get won’t fail me at all
Hello dear reader, and as @silent_mobius refers to, gentle readers, welcome!
My name is Alex Umansky, aka TheBlueDrara, and I welcome you to a small and simple guide I wrote on Linux repository mirroring.
This guide was inspired by a task I received in a project where I had to localize many packages in my environment, as sooner or later the internet on my poor, poor Linux server would be cut off.
So here we are — shall we begin?
Overview
For starters, let’s understand what we are going to do here.
We will be creating a mirror of the official Ubuntu repository, so we can install packages in an offline environment.
And since not all packages come from the official Ubuntu repository, some packages will need to be downloaded manually and stored in our own repo — a so-called “flat” repository.
I’ll explain the difference between them in detail in the upcoming steps.
Prerequisites
Before we begin, we need to install some prerequisites to be able to mirror repositories and expose our local mirror so we can pull packages from our mirror server.
We will need:
-
apt-utils
(to generate the files we need) -
nginx
(a web server to expose our local mirror) -
debmirror
(a tool to create a local Debian/Ubuntu mirror) -
gnupg
(to create and manage GPG keys)
apt-get update
apt-get install -y apt-utils nginx debmirror gnupg
We’ll start with the official Ubuntu mirror.
We’ll use debmirror, a tool that mirrors a remote repo using parameters and an upstream URL.
Important: This method works not only with the Ubuntu repository but also with other vendor repositories, as long as you have the repo URL and the GPG key.
Before mirroring, it’s useful to understand how Debian/Ubuntu repos are built.
They have a special file structure that we are keen to learn.
They are organized into Suites and Components.
-
Suites are distributions or releases of packages, for example:
-
noble
→ The base release of Ubuntu 24.04 -
noble-updates
→ Stable updates after the initial 24.04 release
-
-
Components are sections grouped by license/support status, such as:
-
main
→ Official Canonical-supported open source -
restricted
→ Proprietary drivers/firmware -
universe
→ Community-maintained software -
multiverse
→ Non-free software
-
If you’re unsure what packages you need and have the storage space, you can simply mirror them all — but keep in mind the whole repo may take around ~2 TB of storage space.
Sometimes it’s better to start with a small mirror of the main
component of each suite and see if you’re not missing any needed dependencies.
Creating a Debian Repository
Let’s start by creating a directory to store our repository structure:
sudo mkdir -p /storage/ubuntu
The official Ubuntu repo URL we will use is:
http://archive.ubuntu.com/ubuntu/
In this example, we’ll mirror the noble
and noble-updates
suites, and only the main
component from each one:
debmirror /storage/ubuntu \
--nosource \
--progress \
--host=archive.ubuntu.com \
--root=ubuntu \
--method=http \
--dist=noble,noble-updates \
--section=main \
--arch=amd64 \
--ignore-release-gpg
Note: For safer practice, remove
--ignore-release-gpg
and instead add the vendor’s GPG key to/usr/share/keyrings
so signatures are verified.
The mirroring process may take a while depending on what you mirrored.
Repository File Structure
Before we head forward, it is important to understand the file structure of a Debian repo.
Once finished downloading, you’ll see three main directories:
-
project
→ global metadata (mostly Debian-specific, so we will set this aside for now) -
dists
→ contains the suites likenoble
,noble-updates
-
pool
→ contains all the.deb
packages
pool
A directory that holds all of our .deb
packages.
They can be sorted alphabetically in directories or simply stored together.
It doesn’t matter for APT — it only depends on your preference, nice and tidy like the Inquisition of Mankind, or chaotic like the Chaos Warbands.
dists
This directory contains:
- suite directories (different releases like
noble/
), and inside each suite are component directories likemain/
,restricted/
, etc. - repository metadata files:
Packages
,Release
,InRelease
,Release.gpg
The Heart of the APT Repo
As said before, inside each dists
directory there are metadata files: Packages
, Release
, InRelease
, Release.gpg
.
These files are the beating heart of the repo. They are what make APT come alive.
Like my most favorite album, Holy Diver by Ronnie James Dio (bless his soul):
“Between the velvet lies, there’s a truth as hard as steel.”
Let’s find our truth.
Packages
file
An index file that lists all .deb
packages with:
- names, file paths, checksums, dependencies
This file tells the repo what .deb
packages exist, what they depend on, and where they are located in the repo.
If you add or remove packages from the pool
directory, you must recreate the Packages
file to re-index the packages.
Release
file
Summarizes the repository:
- checksums of
.deb
Packages - defined distributions/components that exist in the repo
- references to the index files (like
Packages
) - contains metadata of the repo
Every change in the repo should be accompanied by regenerating the Release
file.
InRelease
and Release.gpg
Signed versions of the Release
file:
-
Release.gpg
→ older detached signing method -
InRelease
→ newer inline signing method
To summarize this important section: each time you run apt-get update
, you are pulling two files from a remote repo: the signed InRelease
and the Packages
files — so you can install the packages inside those repos.
Exposing the Local Repo
If we want to be able to pull packages from our mirror, we need to make the repo available to others.
In this example we’ll expose it with Nginx.
Let’s create a config file for Nginx:
vim /etc/nginx/sites-available/ubuntu-mirror
Add:
server {
listen 80;
listen [::]:80;
server_name _;
location /ubuntu {
alias /storage/ubuntu;
autoindex on;
try_files $uri $uri/ @notfound;
}
location /my-local-repo {
alias /storage/my-local-repo;
autoindex on;
try_files $uri $uri/ @notfound;
}
location @notfound {
return 404;
}
}
Enable it and restart the service:
sudo ln -s /etc/nginx/sites-available/ubuntu-mirror /etc/nginx/sites-enabled/
systemctl restart nginx
Now the repo is available and can be sourced. We’ll touch on how to source it soon.
Creating a GPG Key
Signing your repo is not strictly necessary, but it’s a nice practice for automation and branding your repo.
If you don’t want to do this step, you can skip ahead.
To sign the repo with our own key, we use the gnupg
tool.
Generate a key:
gpg --gen-key
List the key to see the PUB KEY ID:
gpg --list-keys
Signing the Repo
Remember: in each dist (suite) there are its own metadata files you need to sign.
gpg --local-user "<PUB_KEY_ID>" --yes --clearsign -o dists/<dist_name>/InRelease dists/<dist_name>/Release
gpg --local-user "<PUB_KEY_ID>" --yes --detach-sign -o dists/<dist_name>/Release.gpg dists/<dist_name>/Release
Export the key into a file so we can pass it to the clients:
gpg --armor --export <KEY_PUB_ID> > <local_repo_key_name>.asc
sudo gpg --dearmor -o /usr/share/keyrings/<local_repo_key_name>.gpg <local_repo_key_name>.asc
sudo chmod 644 /usr/share/keyrings/<local_repo_key_name>.gpg
And… you sold your soul to the devil — congrats!
Let’s continue with the great work so far.
Client Setup
When you run apt-get update
, how does APT know where to pull metadata files from?
Quite simply — there is a source file that APT reads.
On the client, let’s create or edit a source file:
vim /etc/apt/sources.list.d/ubuntu.sources
Add the config of our mirrored repo:
Types: deb
URIs: http://<IP_or_DNS_of_mirror_server>/ubuntu/
Suites: noble noble-updates
Components: main
Architectures: amd64
Signed-By: /usr/share/keyrings/<key_name>.gpg
Place the GPG key where APT expects it:
mv <my_local_repo_key>.gpg /usr/share/keyrings/
Update APT:
apt-get update
APT stores the metadata it pulls in:
/var/lib/apt/lists
You can check if packages are present in the repo with:
apt-cache search <package_name>
apt-cache show <package_name>
In the newer OS versions you should use
apt show
apt search
Note: make sure there is network connectivity to the server running the local repo.
Creating a Flat Repository
But what if I want to create a local stash of random .deb
packages I need from vendors, tools, or my own creations?
A flat repo is your answer.
A flat repo is super simple: just a directory that contains .deb
packages plus the metadata files we learned about before (Packages
and Release
), all in one place.
Messy? A bit. But it works.
Create Packages
Because this is a flat repo, we don’t get ready-made metadata. We need to create it ourselves.
Note: these steps also work for a Debian repo, just with different paths.
cd <flat_repo_dir>
apt-ftparchive packages <flat_repo_dir> | tee <flat_repo_dir>/Packages
For a Debian repo (pool-based):
cd <debian_repo_dir>
apt-ftparchive packages ./pool | tee dists/<suite>/<component>/binary-<Arch>/Packages
Sometimes we need to compress the file:
gzip -9c Packages > Packages.gz
Create Release
I like to create a template I can edit later for ease of use:
cat > release.conf <<'EOF'
APT::FTPArchive::Release {
Origin "<Custom_Origin>";
Label "<Custom_Label>";
Suite "<Custom_Suite>";
Version "<Version>";
Codename "<Custom_Codename>";
Architectures "<Architecture_for_example_amd64>";
Components "main";
Description "<Whatever_you_wish>";
}
EOF
Now generate the file:
cd <flat_repo_dir>
apt-ftparchive -c <Path_to_release.conf> release <path_to_root_directory> > <path_to_root_directory>/Release
Sign it:
gpg --local-user "<PUB_KEY_ID>" --yes --clearsign -o InRelease Release
gpg --local-user "<PUB_KEY_ID>" --yes --detach-sign -o Release.gpg Release
Exposing a Flat Repo
In the first step we already added to Nginx the configuration to expose this local flat repo.
Client Setup for Flat Repo
The source file looks a little different here.
We remove the component part, and for the suite we just mention ./
as the root.
Don’t forget to add your repo key!
vim /etc/apt/sources.list.d/flatrepo.sources
Add:
Types: deb
URIs: http://<IP_or_DNS_of_repo>/<Repo_name>
Suites: ./
Architectures: amd64
Signed-By: /usr/share/keyrings/<Key_name>.gpg
Update APT:
apt-get update
I hope my work and writing helped a hopeless soul somewhere in the vast DevOps universe. Thank you for taking the time to read my work!
TheBlueDrara
Top comments (4)
will there be ansible playbook to set it up ?
I may create an ansible playbook to deploy an offical ubuntu mirror on a server at will, thats a good idea
what about debian mirror ?
it can be moduler, a different role to step up a different purpose repo, it will be like using a tool based on ansible