Reproducible Debian package builds with integrated signing and Aptly publishing.
packtly-builder is the build component of the Packtly platform. It wraps the Debian packaging toolchain (debuild, dpkg-buildpackage, lintian, GPG) inside rootless Podman containers and drives it with a small Python CLI, so a build behaves the same on a laptop, in CI, and on a release machine.
Pair it with packtly-infra β which stands up the Aptly repository, HTTP endpoint, and credentials packtly-builder publishes to β for a complete, self-hosted APT package pipeline.
debuild builds a package on whatever host you happen to run it on, with whatever toolchain, GPG keys, and dependencies happen to be installed there. packtly-builder:
- Builds inside a pinned, disposable container, so builds are reproducible across machines and CI runners.
- Wraps build β sign β publish as a single command instead of a chain of manual
dpkg-buildpackage,debsign, andaptlyinvocations. - Understands Aptly: it checks what's already published, skips redundant uploads, and publishes source alongside binaries.
- Needs only Podman,
podman-compose, andjuston the host β the packaging toolchain itself never touches the host.
- 1. packtly-builder
- Container-native Debian package builds
- Reproducible build environments
- Binary and source package generation
- Multi-architecture support (amd64, arm64, armhf)
- Automatic GPG package signing
- Automatic Aptly publishing
- Debian source package publishing
- VS Code Dev Container
flowchart LR
A["π Debian Source Tree"]
A --> C
subgraph B["π¦ packtly-builder"]
C["π¨ Build"]
D["π Sign"]
C --> D
D --> E{"Upload?"}
end
E -->|No| F["π¦ Local Artifacts"]
E -->|Yes| G["π Aptly Repository"]
G --> H["π» Debian"]
G --> I["π€ CI/CD"]
The build pipeline creates reproducible Debian packages inside isolated containers, signs the resulting artifacts with GPG, and optionally publishes them to an Aptly repository for immediate consumption by Debian systems, CI/CD pipelines, and embedded Linux build systems.
A build produces one or more of the following artifacts in different architectures (amd64, arm64, armhf):
- Debian binary packages (
.deb) - Debian source packages (
.dsc) - Original source archives (
.orig.tar.*) - Debian packaging archives (
.debian.tar.*) - Build metadata (
.changes,.buildinfo) - GPG-signed release artifacts
- Multi-architecture package builds
The packtly-builder runtime image includes packtly_builder_tooling as its ENTRYPOINT, so starting the container is equivalent to invoking the CLI directly.
A typical build is performed with a single podman run command that mounts the Debian source tree, GPG signing keys, Aptly credentials, and a log directory into the container:
podman run --rm \
--platform "linux/amd64" \
-v "$PWD":/workspace:Z \
-v "$KEYS_DIR/public/repo_signing.key":/opt/keys/gpg/repo_signing.key:Z,ro \
-v "$KEYS_DIR/private/repo_signing_private.key":/opt/keys/gpg/repo_signing_private.key:Z,ro \
-v "$KEYS_DIR/private/repo_signing_private_pass":/opt/keys/gpg/repo_signing_private_pass:Z,ro \
-v "$APTLY_CREDENTIALS_FILE":/run/secrets/aptly-credentials:Z,ro \
-v "$PWD/logs":/logs:Z \
-e APTLYHOST=http://localhost:8080 \
--network=host \
ghcr.io/packtly/packtly-builder:latest \
/workspace/debhello-quilt \
--log-file /logs/build.log \
--dist trixie-apollo \
--component main \
--credentials-file /run/secrets/aptly-credentials| Mount / flag | Purpose |
|---|---|
-v <source>:/workspace |
Mounts the Debian source tree. This path is passed to the CLI as the builddir argument |
-v .../repo_signing*.key:/opt/keys/gpg/... |
Mounts the GPG signing key material read-only. |
-v <credentials>:/run/secrets/aptly-credentials |
Mounts the Aptly REST API credentials read-only. |
-v <logs>:/logs |
Stores build logs generated by --log-file on the host. |
-e APTLYHOST=... |
Specifies the Aptly API endpoint. |
--network=host |
Allows the container to communicate directly with a locally running Aptly instance. |
For a complete example, see test/fixtures/build-quilt.sh The script wraps the container invocation into convenient
into convenient build, upload, force-upload, and all actions. The --no-build --upload combination reuses the same runtime image to sign and publish previously built artifacts without rebuilding the package.
packtly-builder is published as a multi-architecture container image. The target architecture is selected entirely through Podman's --platform optionβthere is no separate --arch flag in the CLI.
| Platform | Target architecture |
|---|---|
| --platform | linux/amd64 |
| --platform | linux/arm64 |
| --platform | linux/arm/v7 |
Internally, packtly_builder_tooling detects the architecture it is running under (via platform.machine()) and configures the build accordingly, including selecting the appropriate Aptly repository endpoints.
The container itself is built per architecture (see Container Images); which architecture a given podman run builds for is controlled entirely by Podman's --platform flag β the CLI has no separate --arch option. It simply detects the architecture it is actually running under (platform.machine()) and acts accordingly (e.g. when checking/uploading to the matching Aptly endpoint).
For example, to build for arm64:
podman run \
--platform linux/arm64 \
... \
ghcr.io/packtly/packtly-builder:latest \
/workspace/debhello-quiltOn an amd64 host, running linux/arm64 or linux/arm/v7 containers requires QEMU user-mode emulation via binfmt_misc (typically provided by the qemu-user-static and binfmt-support packages), allowing foreign-architecture binaries to execute transparently inside the container.
The reference script test/fixtures/build-quilt.sh
exposes the target architecture as its second argument (default: amd64):
./build-quilt.sh build arm64The script performs a full source package build only for amd64, as the architecture-independent source package needs to be created only once. Subsequent architecture builds reuse the same source package and produce only the architecture-specific binary packages.
In CI, this approach scales naturally to a build matrix with one job per target architecture. Native runners are used where available, while QEMU emulation is only required for armhf. The resulting architecture-specific images are finally assembled into a single multi-architecture OCI manifest, allowing ghcr.io/packtly/packtly-builder:latest to automatically resolve to the correct image for the host platform.
The runtime image provides the packtly_builder_tooling command.
packtly_builder_tooling <builddir> [options]
| Option | Description |
|---|---|
builddir |
Path to the Debian build directory (positional, required) |
--build-mode {binary,source,full} |
What to build: binary (default), source, or full (source + binary) |
--no-build |
Skip the build step (sign/upload existing artifacts) |
--aptlyhost URL |
Aptly REST API base URL (falls back to the APTLYHOST env var) |
--dist NAME |
Aptly publish distribution (e.g. trixie-apollo) |
--component NAME |
Aptly component (e.g. main) |
--credentials-file PATH |
Aptly credentials file (default: /run/secrets/aptly-credentials) |
--upload |
Upload the built package to Aptly after signing |
--force-upload |
Upload even if the package already exists upstream |
--log-file PATH |
Also write log output to this file |
--verbose, -v |
Enable debug logging |
| Mode | Description |
|---|---|
binary |
Build binary packages only |
source |
Build Debian source package |
full |
Build source and binary packages |
The CLI can optionally:
- sign packages with GPG
- upload artifacts to an Aptly repository
- skip packages already published
- force uploads when required
Repository signing keys are mounted into the container at:
/opt/keys/gpg/
The builder automatically imports the configured signing key and uses it for:
- Debian package signing
- Source package signing
- Repository uploads
Aptly API credentials are read from:
/run/secrets/aptly-credentials
making the builder suitable for local development and CI/CD environments.
packtly-builder consists of three major components:
| Component | Description |
|---|---|
| Container Images | Layered Podman images containing the complete Debian packaging toolchain. |
| packtly_builder_tooling | Python CLI for building, signing, and publishing Debian packages. |
| CI/CD Pipeline | Automated image builds, testing, multi-architecture releases, and package publishing. |
Build the complete runtime image:
git clone https://github.com/packtly/packtly-builder.git
cd packtly-builder
just allOr execute each stage individually:
just build-builder
just test-tooling
just build-tooling
just build-runtimeList all available tasks:
just| Image | Purpose |
|---|---|
packtly-builder-base |
Debian packaging toolchain |
packtly-builder-builder |
Build and test environment |
packtly-builder |
Runtime image for package builds |
packtly-builder-devcontainer |
VS Code Dev Container |
All images support amd64 and arm64.
packtly-builder/
βββ tooling/ # Python CLI
βββ tests/ # Unit and integration tests
βββ container/ # Container definitions
βββ Keys/ # Development signing keys
βββ scripts/ # Helper scripts
βββ justfile # Development workflow
βββ Containerfile # Runtime image
βββ .github/workflows/ # GitHub Actions
| Target | Description |
|---|---|
just / just list |
List all available recipes |
just build-base [arch] |
Build the base toolchain image |
just build-builder [arch] |
Build the build/test environment image |
just build-runtime [arch] |
Build the runtime image |
just build-devcontainer [arch] |
Build the VS Code Dev Container image |
just build-builder-multiarch / just build-runtime-multiarch |
Build and assemble multi-arch manifests (amd64 + arm64) |
just build-tooling |
Build the packtly_builder_tooling Python wheel inside the builder container |
just test-tooling [arch] |
Run the tooling test suite inside the builder container |
just shell |
Drop into an interactive shell in the builder container |
just clean-containers / just clean |
Remove built images / build artifacts |
just lint-robot |
Lint Robot Framework test suites with robocop |
just all |
Full pipeline: build-builder β test-tooling β build-tooling β build-runtime-multiarch |
The project ships with a complete VS Code Dev Container (packtly-builder-devcontainer), pre-configured with Poetry, the Debian packaging toolchain, and the linting/test tools used by CI. Open the repository in VS Code and select Reopen in Container to get a ready-to-use development environment without installing anything on the host beyond Podman.
For local development of the packtly_builder_tooling CLI:
cd tooling
just prepare
just test
just pytest
just mypy
just flake8Release versions are derived from CHANGELOG.md using semantic versioning.
CI automatically:
- builds multi-architecture images
- executes the test suite
- publishes container images
- creates GitHub releases
| Project | Description |
|---|---|
| Packtly | Platform overview |
| packtly-builder | Container-native Debian package builder (this repository) |
| packtly-infra | Deploy and operate an Aptly repository platform |