Quickstart: Docker Compose
Test (completely open, one command)
git clone https://git.morlana.online/f.weber/aptly-containerized.git
cd aptly-containerized
docker compose -f compose/docker-compose.test.yaml up -d --build
That's it. No auth, no GPG, ephemeral volumes. After a few seconds:
curl http://localhost:8080/ # repo index (empty until you push something)
curl http://localhost:8080/api/ready # {"Status":"Aptly is ready"}
A demo repo demo (distribution stable, component main) is already created and
published (compose/config/state.test.yaml) — it just doesn't have any packages yet.
Build and push your own package (see packaging for details):
docker build -f images/aptly-deb-builder/Dockerfile -t aptly-deb-builder .
docker run --rm -v "$PWD/my-project:/work" -w /work aptly-deb-builder \
aptly-release --config nfpm.yaml -- \
--repo demo --distribution stable --no-sign
curl http://localhost:8080/dists/stable/main/binary-amd64/Packages
Tear down: docker compose -f compose/docker-compose.test.yaml down -v.
Production
cd compose
cp .env.example .env # edit: at minimum set APTLY_INTERNAL_PASSWORD
cp config/users.example config/users # edit: real credentials for write access
mkdir -p config/gpg # optional: drop private.asc (+ passphrase) in here
docker compose up -d
docker compose logs -f aptly-init # check: users hashed correctly? GPG key found?
The default security mode is publicRead: reading (apt clients) is open, writing
(/api/) needs Basic Auth. Other modes and the pitfalls around
security.write.allowCIDRs behind a reverse proxy are covered in
security.
Declare your own repos/mirrors/publish targets in compose/config/state.yaml (a
starter template is already there) — the reconcile service in docker-compose.yaml
converges towards it on every start, without touching existing packages.
Storage resizing, backup/restore, GPG key rotation: see operations.
Quickstart: Helm
Installation
helm registry login git.morlana.online # if the chart stays private
helm install aptly oci://git.morlana.online/f.weber/aptly --version <chart-version> \
--set security.preset=publicRead \
--set ingress.enabled=true \
--set ingress.repo.host=apt.example.com
Without ingress.enabled you immediately get a kubectl port-forward hint printed
in the Helm NOTES — nothing needs to be configured up front to try the chart locally
(e.g. in kind/k3d).
Configuring it especially easily — the three axes
-
Security — one switch:
security: preset: open # | publicRead (default) | authenticated | readOnlyFull matrix including the Ingress
splitvariant and thetrustedProxies/allowCIDRspitfalls: security.md. -
aptly configuration — curated keys + passthrough:
aptly: architectures: [amd64, arm64] metrics: enabled: true configOverrides: # any aptly config key, 1:1, always wins s3_publish_endpoints: cdn: { region: eu-central-1, bucket: apt-morlana }configOverridesis merged over the generated config last — nothing in aptly's own configuration is ever unreachable through this chart. -
Declarative state — repos/mirrors/publish in
values.yaml:aptly: localRepos: - { name: stable, defaultDistribution: stable, defaultComponent: main } publish: - name: stable-root prefix: "" distribution: stable sourceKind: local sources: [{ name: stable, component: main }] architectures: [amd64, arm64]A Helm hook Job reconciles this against the running instance after every
helm install/helm upgrade— see packaging.md for the exact semantics (idempotency, what can NOT be changed after the fact, prefix escaping).
Health check
kubectl exec <release>-aptly-0 -c aptly -- curl -fsS http://127.0.0.1:8080/api/ready
kubectl logs job/<release>-aptly-reconcile # only visible right after install/upgrade
Storage resizing
persistence.size on an already-installed StatefulSet is immutable — see
operations for the recovery procedure, and set
persistence.existingClaim from the start in production instead of the
chart-generated volumeClaimTemplate.