Deployment guide

aptly

Overview

aptly is a 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 can verify and consume.

This guide covers deploying and configuring aptly on Ubuntu 24.04 on Azure, using the SecureAnyCloud image from the Azure Marketplace. 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. The aptly REST API is bound to the loopback interface only (127.0.0.1:8080), and nginx is the sole network-facing surface.

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. It then publishes a demonstration repository signed with that per-instance key.

What you get:

  • 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. aptly has no first-party web interface, so you drive it with the command line and the REST API.
  • nginx (nginx.service) terminating TLS on port 443 as the only network-facing service. It fronts the management API with HTTP basic authentication and serves your published repositories read-only to APT clients.
  • 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.asc so clients can trust it
  • The full aptly command line and REST API for mirroring, snapshotting and publishing your own repositories
  • Repository trust that comes from GPG signatures, which is the standard APT security model. Serving the repositories read-only over HTTPS is therefore normal and safe, and the management API cannot be reached without the per-instance password.

Prerequisites

Before you deploy this image, make sure you have:

  • An active Azure subscription, an SSH public key, and a VNet plus subnet in the target region
  • A subscription to the aptly listing on the Azure Marketplace
  • Network Security Group rules that allow 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. Mirroring large upstream repositories is disk and bandwidth intensive, so increase the size and attach a larger data disk for that workload.

1

Deploy from the Azure Portal

Search the Marketplace for aptly, select the SecureAnyCloud publisher, and select 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 port 443 restricted to the networks you serve. A package repository rarely needs to be reachable from the whole internet.

2

Deploy from the Azure CLI

RG="aptly-prod"; LOCATION="eastus"; VM_NAME="aptly1" IMAGE_URN="sac1647283583153:aptly-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:aptly-ubuntu-24-04:default:latest
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$IMAGE_URN" \
  --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
3

First boot and per-instance credentials

On first boot, the image resolves this VM's IP and generates a per-instance GPG signing key, a per-instance management API password and a per-instance TLS certificate. It 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. It 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.

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 '
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, and 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.

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 that 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 sac-aptly-demo that was added on first boot.

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. This is exactly the check apt performs on every repository update, and it is the integrity guarantee that lets the repositories be served read-only.

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 with a DNS name that 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/sac-aptly.list
sudo apt-get update

For production, put a real DNS name and a CA-signed certificate in front of the repository, replacing the per-instance self-signed certificate in /etc/nginx/tls. Clients then do not need to trust a self-signed certificate.

9

Create and publish your own repository

You manage aptly through its REST API. The examples below create a repository. 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. The API server holds the aptly database while it runs, so stop it with sudo systemctl stop aptly before you run aptly commands directly, then start it again afterwards. For automation, prefer the REST API.

10

Mirror and snapshot upstream repositories

Mirroring and snapshotting are aptly's core strengths. 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 it is available through the REST API on this instance.

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 of the repository 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, because anyone who holds it can sign packages that your clients will trust.

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

12

Support and Licensing

aptly is open-source software distributed under the MIT License. This SecureAnyCloud image bundles the unmodified official aptly release binary. SecureAnyCloud provides the packaging, the systemd hardening, the nginx TLS front, the loopback-only API with per-instance basic authentication, the per-instance signing key and credential automation, the paired deployment guide, and 24/7 support with a guaranteed 24-hour response SLA.

SecureAnyCloud 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 SecureAnyCloud. Deploy from the Portal or the Azure CLI as shown above.

Need Help?

Email sales@secureanycloud.com for deployment help, configuration questions or licensing enquiries.

Deploy on Azure

Launch aptly 1.6.3 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure