Developer Tools AWS

aptly APT Package Repository Manager on AWS User Guide

| Product: aptly

Overview

This guide covers the deployment and operation of aptly on AWS using the cloudimg AWS Marketplace listing. aptly is a self hosted tool for managing APT package repositories: it mirrors upstream repositories to a fast local copy, snapshots them so a fleet always installs an identical, reproducible set of packages, and publishes your own GPG signed repositories that any apt client verifies and consumes.

The image installs aptly 1.6.3 from the official upstream release (a single self contained Go binary, MIT licensed) and runs it under systemd behind nginx. aptly has no first party web interface: it is driven by a command line and a REST API. In this image the aptly REST API is bound to the loopback interface only (127.0.0.1:8080) and nginx is the sole network facing surface. nginx terminates TLS on port 443, fronts the management API with HTTP basic authentication, and serves your published repositories read only to apt clients. Port 80 redirects to HTTPS.

Secure by default, no shared credentials. Nothing secret is baked into the image. On the first boot of every instance, aptly generates a unique GPG signing key, a unique HTTP basic authentication password for the management API, and a unique self signed TLS certificate, then publishes a demonstration repository signed with that per instance key. The management API cannot be reached without the per instance password, and the published repositories are read only. Repository trust comes from GPG signatures, which is the standard apt security model, so serving the repositories read only over HTTPS is both normal and safe.

What is included:

  • aptly 1.6.3 from the official upstream release binary, run under systemd (aptly.service) as an unprivileged aptly user with the REST API bound to 127.0.0.1:8080

  • nginx (nginx.service) terminating TLS on port 443 as the only network facing service, fronting the management API with HTTP basic auth and serving published repositories read only

  • A per instance GPG signing key, management API password and TLS certificate, all generated fresh on first boot, with the per instance values written to /root/aptly-credentials.txt (mode 0600 root:root)

  • A signed demonstration repository published on first boot, and the exported public key at /aptly-repo-key.asc so clients can trust it

  • The full aptly command line and REST API for mirroring, snapshotting and publishing your own repositories

  • 24/7 cloudimg support

Prerequisites

An AWS account, an EC2 key pair, and a VPC subnet. The security group should allow inbound TCP 22 (SSH) from your administration network, and TCP 443 (published repositories and the management API) plus TCP 80 (redirect to HTTPS) from the client machines and build agents that consume your repositories. A package repository rarely needs to be reachable from the whole internet.

m5.large is a comfortable default. aptly itself is a small Go binary, so the sizing driver is disk and bandwidth: mirroring large upstream repositories is storage intensive. Grow the root volume, or attach and mount your own EBS volume at /var/lib/aptly, before you start mirroring.

Connecting to your instance

SSH in as the default login user for the operating system variant you launched. This listing currently ships one variant:

OS variant SSH login user
Ubuntu 24.04 LTS ubuntu
ssh ubuntu@<public-ip>

Step 1: Launch from AWS Marketplace

Subscribe to the cloudimg aptly listing and launch an instance. Pick m5.large, choose your VPC and subnet, and attach a security group allowing inbound TCP 22 from your administration network and TCP 443 and 80 from the hosts that will consume the repositories.

Step 2: First boot and per instance credentials

On first boot the instance resolves its own public address from EC2 instance metadata, generates a per instance GPG signing key, a per instance management API password and a per instance TLS certificate, publishes the signed demonstration repository, and writes everything to /root/aptly-credentials.txt. This completes within seconds of the instance becoming reachable.

The file is 0600 root:root. The block below lists it and prints the non secret entries; read the API password itself with sudo cat /root/aptly-credentials.txt when you need it, and keep it off your terminal history and out of screenshots.

sudo ls -l /root/aptly-credentials.txt
sudo grep -E '^APTLY_(API_USER|API_URL|REPO_URL|REPO_DISTRIBUTION|REPO_COMPONENT|REPO_PUBKEY|GPG_KEYID)=' /root/aptly-credentials.txt
-rw------- 1 root root 1494 Jul 25 20:58 /root/aptly-credentials.txt
APTLY_API_USER=aptly
APTLY_API_URL=https://54.163.186.39/api/version
APTLY_REPO_URL=https://54.163.186.39/
APTLY_REPO_DISTRIBUTION=stable
APTLY_REPO_COMPONENT=main
APTLY_REPO_PUBKEY=https://54.163.186.39/aptly-repo-key.asc
APTLY_GPG_KEYID=BB9D786F6E7D0B13

Every value above is unique to the instance, including the GPG key id. Two instances launched from this listing never share a signing key or an API password.

Step 3: Confirm the services are running

aptly.service and nginx.service are both active, aptly version reports 1.6.3, and ss confirms that nginx owns port 443 while the aptly REST API is bound to loopback only on port 8080.

sudo systemctl is-active aptly nginx
/opt/aptly/aptly version
sudo ss -tlnp | grep -E ':443 |:8080 '
active
active
aptly version: 1.6.3
LISTEN 0      4096       127.0.0.1:8080      0.0.0.0:*    users:(("aptly",pid=2841,fd=3))
LISTEN 0      511          0.0.0.0:443       0.0.0.0:*    users:(("nginx",pid=2854,fd=7),("nginx",pid=2853,fd=7),("nginx",pid=2852,fd=7))
LISTEN 0      511             [::]:443          [::]:*    users:(("nginx",pid=2854,fd=8),("nginx",pid=2853,fd=8),("nginx",pid=2852,fd=8))

aptly.service and nginx.service both report active, the aptly binary reports version 1.6.3, and ss shows nginx listening on port 443 on all interfaces while the aptly REST API is bound to loopback 127.0.0.1 on port 8080 only

Step 4: Use the management API

The management REST API is reachable only through nginx on port 443, and only with the per instance HTTP basic authentication. An unauthenticated request returns 401; a request with the per instance credentials returns 200. The block below reads the credentials into shell variables straight from the instance credentials file, so it works unchanged on any instance and never prints the password.

API_USER=$(sudo grep -E '^APTLY_API_USER=' /root/aptly-credentials.txt | cut -d= -f2-)
API_PASS=$(sudo grep -E '^APTLY_API_PASSWORD=' /root/aptly-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'no auth  -> HTTP %{http_code}\n' https://127.0.0.1/api/version
curl -sk -u "$API_USER:$API_PASS" -w '\n' https://127.0.0.1/api/version
curl -sk -u "$API_USER:$API_PASS" -w '\n' https://127.0.0.1/api/repos
no auth  -> HTTP 401
{"Version":"1.6.3"}
[{"Name":"demo","Comment":"","DefaultDistribution":"stable","DefaultComponent":"main","NumPackages":1}]

The -k flag tells curl to accept the self signed per instance certificate. The /api/repos call lists the repositories aptly is managing, starting with the demo repository published on first boot with one package in it.

the management API returns HTTP 401 for an unauthenticated request and HTTP 200 with version 1.6.3 when the per instance basic auth credentials are supplied, and the authenticated repos endpoint lists the demo repository holding one package

Step 5: Confirm the API refuses unauthenticated callers

This is the security property that matters most on a repository server, so it is worth asserting rather than assuming. The block below expects the call to be refused and exits 0 only when it is.

if curl -skf -o /dev/null https://127.0.0.1/api/repos; then
  echo "UNEXPECTED: unauthenticated management API answered"
  exit 1
else
  echo "unauthenticated management API refused (exit $?) - correct"
fi
unauthenticated management API refused (exit 22) - correct

The aptly API process itself never accepts a connection from the network. The block below lists every listening socket on the instance: port 8080 appears only on 127.0.0.1, so the only route to the API is through the authenticated nginx location.

sudo ss -tlnH | awk '{print $1, $4}' | sort -u
LISTEN 0.0.0.0:22
LISTEN 0.0.0.0:443
LISTEN 0.0.0.0:80
LISTEN 127.0.0.1:8080
LISTEN 127.0.0.53%lo:53
LISTEN 127.0.0.54:53
LISTEN [::]:22
LISTEN [::]:443
LISTEN [::]:80

Step 6: The published, signed repository

The demonstration repository is published and served read only over HTTPS. Fetch its Release file and its Packages index, both of which are plain files any apt client downloads.

curl -sk https://127.0.0.1/dists/stable/Release
curl -sk https://127.0.0.1/dists/stable/main/binary-amd64/Packages
Origin: . stable
Label: . stable
Suite: stable
Codename: stable
Date: Sat, 25 Jul 2026 20:58:04 UTC
Architectures: amd64
Components: main
Description: Generated by aptly
MD5Sum:
 42cee938057afe0a991fee5a87411f45       80 Contents-amd64
 84645f00a5c752853d346b2a420ae1dd      844 main/binary-amd64/Packages
 f69f4af4b8eaad2b01c382828976c4d1      116 main/binary-amd64/Release
SHA256:
 1d2b4c0e43d010b934556a98cefca50bb96f4c269bc335ee35b0d2c0cd7818fd       80 Contents-amd64
 5b21ecafd3853536764c447c879133502063e4405c5024fff8bee574ea022e8b      844 main/binary-amd64/Packages
 c0cb6579869bb1ecd9caf087b2bc55a4d7a5697c189a7d6d18bd16dac868ed3c      116 main/binary-amd64/Release

Package: cloudimg-aptly-demo
Priority: optional
Section: misc
Maintainer: cloudimg <support@cloudimg.co.uk>
Architecture: all
Version: 1.0
Filename: pool/main/c/cloudimg-aptly-demo/cloudimg-aptly-demo_1.0_all.deb
Size: 880
SHA256: 094431ca5ff466e4cd287c36084f251bc99053c31a30c88837a92a84e5495189
Description: cloudimg aptly demonstration package

The Release file describes the distribution (stable), its component (main) and architecture (amd64) with a checksum for every index file, and the Packages index lists the demonstration package cloudimg-aptly-demo that was added on first boot. The listing above is abridged; the real files also carry SHA1 and SHA512 sections and the compressed index variants.

the published Release file for the stable distribution is served over HTTPS and lists the suite, codename, architectures, components and per file checksums, and the Packages index lists the cloudimg-aptly-demo package with its version, architecture, size and pool filename

Step 7: Verify the GPG signature

The repository is signed by the per instance GPG key, and the public key is exported so clients can verify it. Download the signed InRelease file and the public key, then verify the signature with gpgv.

WORK=$(mktemp -d)
curl -sk https://127.0.0.1/dists/stable/InRelease -o "$WORK/InRelease"
curl -sk https://127.0.0.1/aptly-repo-key.asc -o "$WORK/aptly-key.asc"
gpg --dearmor -o "$WORK/aptly-key.gpg" "$WORK/aptly-key.asc"
gpgv --keyring "$WORK/aptly-key.gpg" "$WORK/InRelease"
gpgv: Signature made Sat Jul 25 20:58:04 2026 UTC
gpgv:                using RSA key 8A52E9C4AFEA266767802B00BB9D786F6E7D0B13
gpgv: Good signature from "cloudimg aptly repository signing key <aptly-repo@ip-172-31-82-48>"

gpgv reports a good signature from the per instance signing key, which is exactly the check apt performs on every repository update. This is the integrity guarantee that lets the repositories be served read only. The key is an RSA 3072 bit signing key generated on this instance and on no other.

gpgv reports a good signature on the InRelease file made with the RSA per instance repository signing key, and the exported public key shows an RSA 3072 bit key with its fingerprint and the cloudimg aptly repository signing key user id

Step 8: Add the repository on a client

On any apt based client, trust the exported public key and add the repository as a signed apt source. Replace <public-ip> with this instance's address, or with a DNS name you point at it.

curl -fsSL https://<public-ip>/aptly-repo-key.asc | sudo gpg --dearmor -o /etc/apt/keyrings/aptly-repo.gpg
echo "deb [signed-by=/etc/apt/keyrings/aptly-repo.gpg] https://<public-ip>/ stable main" | sudo tee /etc/apt/sources.list.d/cloudimg-aptly.list
sudo apt-get update
sudo apt-get install cloudimg-aptly-demo

For production, put a real DNS name and a CA signed certificate in front of the repository (replace the per instance self signed certificate in /etc/nginx/tls) so clients do not need --insecure style exceptions to trust the transport.

Step 9: Create and publish your own repository

You manage aptly through its REST API. The example below creates a repository through the authenticated API; you upload packages and publish through the API as well. Replace <public-ip> and use the credentials for your instance.

curl -sk -u "$API_USER:$API_PASS" -X POST -H 'Content-Type: application/json' \
  -d '{"Name":"myrepo","DefaultDistribution":"noble","DefaultComponent":"main"}' \
  https://<public-ip>/api/repos

The aptly command line is also available for interactive use at /opt/aptly/aptly. Because the API server holds aptly's embedded database while it runs, stop it first with sudo systemctl stop aptly before running aptly commands directly, then start it again afterwards. For automation, prefer the REST API.

Step 10: Mirror and snapshot upstream repositories

aptly's core strengths are mirroring and snapshotting. You create a mirror of an upstream repository, update it to pull the latest packages, snapshot it for a reproducible point in time, and publish the snapshot. Your fleet then installs from the published snapshot, so every machine gets an identical set of packages until you deliberately roll forward to a newer snapshot. The full mirror, snapshot and publish workflow is documented at https://www.aptly.info/doc/ and every step of it is available through the REST API on this instance.

curl -sk -u "$API_USER:$API_PASS" -X POST -H 'Content-Type: application/json' \
  -d '{"Name":"upstream-noble","ArchiveURL":"http://archive.ubuntu.com/ubuntu/","Distribution":"noble","Components":["main"],"Architectures":["amd64"]}' \
  https://<public-ip>/api/mirrors

Step 11: Sizing the repository storage

aptly stores its package pool, its embedded database and the published repository trees under /var/lib/aptly, which is on the root volume by default. Mirroring a full upstream distribution component runs to tens of gigabytes, so check the free space before you start and grow the volume to suit.

df -h /var/lib/aptly
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        29G  3.1G   25G  12% /

To scale beyond the root volume, attach an EBS volume, format it, and mount it at /var/lib/aptly with an /etc/fstab entry that references the filesystem UUID so it survives a reboot. Stop aptly.service before moving the directory and start it again afterwards.

Step 12: Security recommendations

  • Restrict the security group. Allow TCP 443 and 80 only from the client subnets and build agents that consume the repositories, and TCP 22 only from your administration network.

  • Protect the management API password. It lives in /root/aptly-credentials.txt (0600 root:root). The management API is reachable only through nginx and only with this password; rotate it by rewriting /etc/nginx/aptly.htpasswd with htpasswd and reloading nginx.

  • Use a real certificate for production. The per instance certificate is self signed. Put a CA signed certificate and a DNS name in front for production so clients trust the transport without extra configuration.

  • Guard the signing key. The per instance GPG signing key lives in the aptly user's keyring under /var/lib/aptly/.gnupg. Back it up securely; anyone holding it can sign packages your clients will trust.

  • Keep the OS patched. Unattended security upgrades remain enabled on the running instance.

Step 13: Support and Licensing

aptly is open source software distributed under the MIT License. This cloudimg image bundles the unmodified official aptly release binary, verified by SHA256 checksum at build time. cloudimg provides the packaging, the systemd hardening, the nginx TLS front, the loopback only API with per instance basic auth, the per instance signing key and credential automation, this paired deployment guide, and 24/7 support.

cloudimg is not affiliated with or endorsed by the aptly project. aptly is a mark of its respective owner and is used here only to identify the software.

Deploy on AWS

Find aptly on AWS Marketplace, published by cloudimg. Subscribe and launch as shown above.

Need Help?

Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.