Build a Small APT Repository Index with apt-ftparchive
You will turn a directory of Debian packages into a Packages index, compress it for APT clients, and generate a checksummed Release file for the containing archive. The examples use the apt-ftparchive 2.8.3 supplied by Ubuntu's apt-utils package. Allow about 15 minutes if the package directory already exists. The index commands normally run as your own user; use elevated privileges only when your archive is in a directory you cannot read or write.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Check the installed command and choose a staging directory
- 2. Generate an uncompressed Packages file
- 3. Add the compressed index without destroying the original
- 4. Generate a Release file for the archive
- 5. Add archive metadata when clients need it
- 6. Use generate for a repeatable archive job
- 7. Diagnose the usual failures
1. Check the installed command and choose a staging directory
Start by confirming which binary will run and which version its manpage describes:
$ command -v apt-ftparchive
/usr/bin/apt-ftparchive
$ apt-ftparchive --version
apt 2.8.3 (amd64)
Your version may differ. This guide follows the installed 2.8.3 behaviour, where packages, sources, contents, release, generate and clean are separate commands. Make a working copy or staging directory outside the live web root while testing:
$ mkdir -p "$HOME/apt-archive/pool/main" "$HOME/apt-archive/dists/example/main/binary-amd64"
$ cd "$HOME/apt-archive"
Put the .deb files that you intend to publish below pool/main. The packages command searches recursively, so a directory containing unrelated packages will also be indexed. That is a common source of accidental publication.
Checkpoint
Inspect the candidate set before generating anything:
$ find "$HOME/apt-archive/pool" -type f -name '*.deb' -print
2. Generate an uncompressed Packages file
apt-ftparchive packages PATH reads Debian package metadata and writes package records to standard output. Redirect that output to a new index in the directory that matches your archive layout:
$ apt-ftparchive packages "$HOME/apt-archive/pool/main" \
> "$HOME/apt-archive/dists/example/main/binary-amd64/Packages"
The command does not print a success summary. A zero exit status means it completed normally. The output is a text index containing fields such as package name, version, architecture, filename, size and checksums. The filename is derived from the path passed to the command, so run it against the directory structure you will actually publish.
Check that the file exists and contains package records:
$ test -s "$HOME/apt-archive/dists/example/main/binary-amd64/Packages" && \
sed -n '1,24p' "$HOME/apt-archive/dists/example/main/binary-amd64/Packages"
If the file is empty, the search found no readable files with the configured package extension, which defaults to .deb. An empty index can be valid for an empty repository, but it is usually a misplaced directory or a typo in the path.
3. Add the compressed index without destroying the original
APT repositories commonly publish Packages.gz. Compress a temporary file and move it into place only after compression succeeds:
$ gzip -c "$HOME/apt-archive/dists/example/main/binary-amd64/Packages" \
> "$HOME/apt-archive/dists/example/main/binary-amd64/Packages.gz.new" && \
mv "$HOME/apt-archive/dists/example/main/binary-amd64/Packages.gz.new" \
"$HOME/apt-archive/dists/example/main/binary-amd64/Packages.gz"
$ gzip -t "$HOME/apt-archive/dists/example/main/binary-amd64/Packages.gz"
The .new name avoids leaving a half-written compressed index if the command is interrupted. The final mv replaces an existing Packages.gz, so stop first if that file is live and clients may be reading it. To undo this staging example, remove only the generated files after checking the path:
$ rm -- "$HOME/apt-archive/dists/example/main/binary-amd64/Packages.gz" \
"$HOME/apt-archive/dists/example/main/binary-amd64/Packages"
Do not run that removal against a production archive without a backup and a deployment plan. A repository index is derived data, but clients can fail while different index files describe different package sets.
4. Generate a Release file for the archive
The release command walks one archive directory and writes a Release file to standard output. It includes checksums for recognised files such as Packages and Packages.gz. Run it from the archive's dists/example directory and write a new file:
$ apt-ftparchive release "$HOME/apt-archive/dists/example" \
> "$HOME/apt-archive/dists/example/Release.new" && \
mv "$HOME/apt-archive/dists/example/Release.new" \
"$HOME/apt-archive/dists/example/Release"
$ sed -n '1,16p' "$HOME/apt-archive/dists/example/Release"
Typical output begins with a date followed by checksum sections:
Date: ...
MD5Sum:
... binary-amd64/Packages
... binary-amd64/Packages.gz
SHA1:
... binary-amd64/Packages
The exact date, hashes and spacing vary. By default this installed version emits MD5, SHA1, SHA256 and SHA512 sections. The Release file is metadata, not a signature. If clients require repository signing, sign the resulting file through your normal APT repository process; do not treat a checksum as authentication.
5. Add archive metadata when clients need it
APT reads fields such as Origin, Label, Suite, Codename, Architectures and Components from configuration values under APT::FTPArchive::Release. Supply them with repeated -o options while generating the file:
$ apt-ftparchive \
-o APT::FTPArchive::Release::Origin='Example Project' \
-o APT::FTPArchive::Release::Label='Example Packages' \
-o APT::FTPArchive::Release::Suite='example' \
-o APT::FTPArchive::Release::Codename='example' \
-o APT::FTPArchive::Release::Architectures='amd64' \
-o APT::FTPArchive::Release::Components='main' \
release "$HOME/apt-archive/dists/example" \
> "$HOME/apt-archive/dists/example/Release.new" && \
mv "$HOME/apt-archive/dists/example/Release.new" \
"$HOME/apt-archive/dists/example/Release"
$ grep -E '^(Origin|Label|Suite|Codename|Architectures|Components):' \
"$HOME/apt-archive/dists/example/Release"
Keep the values consistent with the APT source entries that clients use. A mismatched suite or component is a repository configuration problem, not something to fix by adding more checksums.
6. Use generate for a repeatable archive job
For one directory, the direct commands are easier to inspect. A scheduled archive normally uses generate and a configuration file. Its configuration uses sections and substitutions rather than shell variables. A minimal shape looks like this:
Dir {
ArchiveDir "/srv/apt-archive";
}
Default {
Packages::Compress ". gzip";
}
BinDirectory "dists/example/main/binary-amd64" {
Packages "dists/example/main/binary-amd64/Packages";
Contents "dists/example/Contents-amd64";
}
That snippet illustrates the configuration vocabulary, but the paths must match your archive and your package layout before you run it. Read apt-ftparchive(1) and apt.conf(5) together, then test with a copy of the archive:
$ apt-ftparchive generate /path/to/your-archive.conf
$ apt-ftparchive clean /path/to/your-archive.conf
Warning
generate can create, replace and compress index files, while clean removes obsolete records from configured cache databases. Review the configuration and generated diff first. Do not point either command at a live archive from an untested cron job.
7. Diagnose the usual failures
- No package records: confirm the files end in
.deb, are readable, and are below the path supplied topackages. The-aoption narrows package files to a requested architecture andall; it can exclude files you expected to see. - Stale size or checksum data: a binary cache database can reuse cached metadata. If packages are rebuilt with the same version, enable
APT::FTPArchive::AlwaysStatfor that run or avoid reusing the version. The manpage describes this as an unusual publication pattern, but it is a real failure mode. - Unexpected warnings: inspect the path and permissions first. Do not hide warnings with
--quietuntil the index contents have been checked. - Clients reject the archive: compare the
Releasefields, the relative filenames inPackages, and the hashes inRelease. Rebuild all derived files from the same package snapshot.
Done means
- The intended
.debfiles appear inPackagesand its compressed copy. gzip -tacceptsPackages.gz.Releaselists hashes for the indexes you publish.- Any Origin, suite, component and architecture values match the client configuration.
- A package snapshot or backup exists before replacing a live archive.