aptly on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of aptly on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. aptly is a powerful, self hosted tool for managing Debian and Ubuntu package repositories: it mirrors upstream APT 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), 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.
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 unprivilegedaptlyuser with the REST API bound to127.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 credentials written to
/root/aptly-credentials.txt -
A signed demonstration repository published on first boot, and the exported public key at
/aptly-repo-key.ascso clients can trust it -
The full aptly command line and REST API for mirroring, snapshotting and publishing your own repositories
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the aptly listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (admin) and TCP 443 (HTTPS, for both the published repositories and the management API) from the clients and administrators that need them
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a departmental repository server. Increase the size and attach a larger data disk when mirroring large upstream repositories, which are disk and bandwidth intensive.
Step 1: Deploy from the Azure Portal
Search aptly in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow TCP 443 from the client machines and build agents that will consume the repositories, and TCP 22 for administration. Keep 443 restricted to the networks you serve; a package repository rarely needs to be reachable from the whole internet.
Step 2: Deploy from the Azure CLI
RG="aptly-prod"; LOCATION="eastus"; VM_NAME="aptly1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/aptly-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 443 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002
Step 3: First boot and per instance credentials
On first boot the image resolves this VM's IP, 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. SSH in as azureuser and read the credentials file:
sudo cat /root/aptly-credentials.txt
The file is 0600 root:root and records the API user and password, the repository and API URLs, the exported public key URL, and the GPG key id. These values are unique to this instance.
Step 4: 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.
systemctl is-active aptly.service nginx.service
/opt/aptly/aptly version
sudo ss -tlnp | grep -E ':443 |:8080 '

Step 5: 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 from the instance credentials file, so it works unchanged on any instance.
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" https://127.0.0.1/api/version; echo
curl -sk -u "$API_USER:$API_PASS" https://127.0.0.1/api/repos; echo
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.

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
The Release file describes the distribution (stable), its components (main) and architectures (amd64), and the Packages index lists the demonstration package cloudimg-aptly-demo that was added on first boot.

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.
curl -sk https://127.0.0.1/dists/stable/InRelease -o /tmp/InRelease
curl -sk https://127.0.0.1/aptly-repo-key.asc -o /tmp/aptly-key.asc
gpg --dearmor < /tmp/aptly-key.asc > /tmp/aptly-key.gpg
gpgv --keyring /tmp/aptly-key.gpg /tmp/InRelease
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.

Step 8: Add the repository on a client
On any Ubuntu or Debian client, trust the exported public key and add the repository as a signed apt source. Replace <vm-ip> with this instance's address (or a DNS name you point at it).
curl -fsSL https://<vm-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://<vm-ip>/ stable main" | sudo tee /etc/apt/sources.list.d/cloudimg-aptly.list
sudo apt-get update
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 to trust the self signed certificate.
Step 9: Create and publish your own repository
You manage aptly through its REST API. The examples below create a repository, and you upload packages and publish through the API as well. Replace <vm-ip> and the credentials with the values for your instance.
# create a new local repository via the authenticated API
curl -sk -u "$API_USER:$API_PASS" -X POST -H 'Content-Type: application/json' \
-d '{"Name":"myrepo","DefaultDistribution":"noble","DefaultComponent":"main"}' \
https://<vm-ip>/api/repos
# upload a package file, add it to the repo, then publish it signed with the per instance key
# (see the aptly REST API docs: https://www.aptly.info/doc/api/ )
The aptly command line is also available for interactive use. Because the API server holds the aptly 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. See the aptly documentation at https://www.aptly.info/doc/ for the mirror, snapshot and publish workflow, all of which is available through the REST API on this instance.
Step 11: Security recommendations
-
Restrict the NSG. Allow TCP 443 only from the client subnets and build agents that consume the repositories, and TCP 22 only from your administration network. A package repository rarely needs to be public.
-
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 updating/etc/nginx/aptly.htpasswd. -
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 with it can sign packages your clients will trust. -
Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
Step 12: Support and Licensing
aptly is open source software distributed under the MIT License. This cloudimg image bundles the unmodified official aptly release binary. 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, the paired deploy guide, and 24/7 support with a guaranteed 24 hour response SLA.
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 Azure
Find aptly on Ubuntu 24.04 LTS on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.
Need Help?
Email support@cloudimg.co.uk for deployment help, configuration questions, or licensing enquiries.