Home / Alt manpages / apt-ftparchive(1)

  • apt-ftparchive(1)
  • User command
  • linux

Build a Small APT Repository Index with apt-ftparchive

Got a folder of .deb files and want APT clients to install from it? apt-ftparchive turns that directory into a Packages index, a compressed copy, and a checksummed Release file. Allow about 15 minutes if the package directory already exists.

The examples use apt-ftparchive 2.8.3, supplied by Ubuntu's apt-utils package. The index commands normally run as your own user; use elevated privileges only if your archive sits in a directory you cannot read or write.

1. Check the command and pick a staging directory

Confirm 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. While testing, work in a staging directory outside the live web root:

$ mkdir -p "$HOME/apt-archive/pool/main" "$HOME/apt-archive/dists/example/main/binary-amd64"
$ cd "$HOME/apt-archive"

Put the .deb files you intend to publish below pool/main. The packages command searches recursively, so a directory containing unrelated packages will index those too. 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 prints no success summary; a zero exit status means it completed normally. The output is a text index with fields such as package name, version, architecture, filename, size and checksums. The filename comes from the path you pass, so run the command 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"

Tip

An empty file means 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 to 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.

Recovery

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"

Warning

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

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 against 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.

Warning

The Release file is metadata, not a signature. If clients require repository signing, sign the resulting file through your normal APT repository process. A checksum is not authentication.

5. Add archive metadata

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"

Tip

Keep the values consistent with the APT source entries your clients use. A mismatched suite or component is a repository configuration problem, not something to fix by adding more checksums.

6. Automate with generate

For one directory, the direct commands are easier to inspect. A scheduled archive normally uses generate and a configuration file, which 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 vocabulary only. The paths must match your archive and 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 sit below the path given to packages. The -a option narrows package files to a requested architecture and all; 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::AlwaysStat for 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 --quiet until you have checked the index contents.
  • Clients reject the archive: compare the Release fields, the relative filenames in Packages, and the hashes in Release. Rebuild all derived files from the same package snapshot.

Done means

  • Right packages indexed. The intended .deb files appear in Packages and its compressed copy.
  • Compression verified. gzip -t accepts Packages.gz.
  • Hashes present. Release lists hashes for the indexes you publish.
  • Metadata matches clients. Origin, suite, component and architecture values agree with the client configuration.
  • Backup taken. A package snapshot or backup exists before you replace a live archive.