Skip to content

Debian (APT) ​

APT repository with dists/ and pool/ layout; indexes and Release files are generated by a background worker and optionally GPG-signed.

Format iddeb
Repo URLHOST/repos/REPO (suite dists/<distribution>/, packages pool/)
Repository typeshosted, proxy, group (details)

Client setup ​

internal/clientsetup produces the sources.list entry and the upload command:

sh
echo "deb [signed-by=/etc/apt/keyrings/REPO.gpg] HOST/repos/REPO stable main" | sudo tee /etc/apt/sources.list.d/REPO.list
sudo apt update

curl -u USERNAME:<TOKEN> -F file=@my-package_1.0_amd64.deb -F distribution=stable -F component=main \
  HOST/api/v1/repositories/REPO/debs

The same snippet first fetches HOST/repos/REPO/public.key into /etc/apt/keyrings/REPO.gpg. The server writes public.key with the repository metadata whenever a signing key applies to the repository (the repository's own key, the global active key, or the GPG_KEY_PATH file; see Signing), so the URL works the same way as for RPM repositories:

sh
curl -fsSL HOST/repos/REPO/public.key | sudo gpg --dearmor -o /etc/apt/keyrings/REPO.gpg

Without a signing key public.key, InRelease and Release.gpg return 404; mark the source [trusted=yes] only on networks you control. A key that is activated (or removed) after packages were uploaded re-signs the repository straight away.

For private repositories apt also needs credentials, for example in /etc/apt/auth.conf.d/ for the host (login USERNAME, password the token).

Upload API ​

POST /api/v1/repositories/REPO/debs (/packages is accepted too and dispatches to the Debian handler for deb repositories). Needs write permission on REPO (RBAC).

Form fieldDefaultNotes
filerequiredthe .deb
distributionstable^[A-Za-z0-9][A-Za-z0-9._\-]*$
componentmainsame pattern
  • Package name, version and architecture come from the .deb control file and must match ^[a-z0-9][a-z0-9+.\-]+$, ^[A-Za-z0-9][A-Za-z0-9.+~:\-]*$ and ^[a-z0-9][a-z0-9\-]*$. An unreadable .deb is 400 invalid deb package.
  • Maximum 500 MiB (413 file exceeds maximum size limit); quota is enforced (quotas).
  • The same SHA-256 in the repository, or an existing name_version_arch.deb (epoch ignored), is 409 CONFLICT with data.duplicate: true; packages are immutable.
  • Success: {"success": true, "data": {id, name, version, architecture, sha256, blob_id, size, distribution, component}}.
  • DELETE /api/v1/repositories/REPO/debs/<id> deletes one package by id (only if it belongs to REPO).
  • Uploads to a non-deb repository are 400; to a proxy repository 405.
  • There is no /repos/REPO/upload route: POST or PUT there answers 404 with a message naming POST /api/v1/repositories/REPO/debs.

Generated layout ​

Served relative to HOST/repos/REPO:

PathNotes
dists/<dist>/ReleaseOrigin/Label openartifact, Suite/Codename = distribution, MD5Sum, SHA1, SHA256
dists/<dist>/InRelease, Release.gpgonly when signing is configured, otherwise 404
dists/<dist>/<comp>/binary-<arch>/Packages, .gz, .xzone per component and architecture
pool/<comp>/<prefix>/<package>/<file>.debpackage download
  • A debounced worker (500 ms after an upload or delete) regenerates the indexes of the repository. Until it has run, Release and Packages* are streamed from the database instead.
  • Architecture all packages are listed in every real architecture index; a distribution with only all packages gets binary-all.
  • Generated files with identical content (for example the empty Packages of two repositories) share one stored blob. A superseded blob is deleted only once no repository's metadata and no package references it, and every run re-checks that the blobs of its committed set still exist, so concurrent regenerations of different repositories cannot leave an index without its file.
  • Package files are looked up by file name, honour quarantine, and are served with Content-Disposition.

Signing ​

Set GPG_ENABLED=true, GPG_KEY_PATH and GPG_PASSPHRASE to sign InRelease and Release.gpg (signing). Without a key apt refuses the repository unless the source line is marked [trusted=yes]. Release.gpg is a standard ASCII-armored detached signature that gpgv and apt verify (gpgv --keyring KEYRING Release.gpg Release); before 0.5.0 it was armored twice and only InRelease was usable. It is rewritten the next time the repository's indexes are regenerated (any upload or delete). Individual .deb files are not signed: apt verifies them through the checksums in the signed Release.

Proxy ​

A Debian proxy repository mirrors dists/ and pool/ from upstream_url. .deb and .udeb files below a pool/ directory (the Debian archive layout, where a file name is never reused) are immutable once cached; the same files in a flat repository, and everything else (indexes, Release) is served from cache until cache_ttl_seconds (default 1 hour) and then revalidated. The upstream's signed InRelease is passed through unchanged, so clients must trust the upstream's key. Negative caching and serve-stale: hosted, proxy, group.

Group ​

A Debian group publishes merged Release, InRelease, Release.gpg and Packages.gz/Packages.xz for each distribution at least one member has, signed with the server key, and serves pool/ files from the member that owns them. Use it like a hosted repository: deb [signed-by=/etc/apt/keyrings/REPO.asc] HOST/repos/GROUP bookworm main, with the key from HOST/repos/GROUP/public.key. Merging rules, regeneration and limits: Deb and RPM groups.

Free software under the AGPL-3.0-or-later.