Developer Tools Azure

Atuin on Ubuntu 24.04 on Azure User Guide

| Product: Atuin on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and use of Atuin on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Atuin is a self hosted sync server for magical shell history. The Atuin terminal client records your shell history into an encrypted, searchable database and, with this server, synchronises it across all of your machines. Because history is encrypted end to end with a key that never leaves your own machines, the server only ever stores opaque encrypted records, so your command history stays private on infrastructure you control instead of a third party service.

The image installs the official prebuilt Atuin server Rust binary, pinned to a release version and checksum verified, and runs it under systemd as a dedicated service bound to the loopback interface. A local PostgreSQL 16 instance is the backing store, and nginx reverse proxies the sync API on port 80, ready for your TLS certificate.

Secure by design — open registration disabled. Atuin's server normally allows anyone to register an account, which turns a public deployment into an open sign up service. This image sets open_registration = false in /etc/atuin/server.toml, so the appliance is single tenant. The guide below shows how to enable registration if you want to invite more people.

Secure by design — no baked credential. Nothing is baked into the image. On first boot the PostgreSQL atuin role password and a per machine Atuin account are generated with independent random values, the account is seeded so the sync API is provably live, and a root only summary is written to /root/atuin-credentials.txt. The application database is dropped when the image is built, so every deployed VM starts from a clean database with fresh credentials. The client side end to end encryption key is always generated on your own machine by the Atuin client and is never stored on the server.

What is included:

  • The official Atuin server binary (atuin-server), checksum verified and run under systemd, bound to the loopback interface

  • A local PostgreSQL 16 backing store, listening on the loopback interface only

  • An nginx reverse proxy on port 80, ready for your TLS certificate

  • Open registration disabled by default, so the server is single tenant rather than an open sign up service

  • A per machine Atuin account and PostgreSQL password generated on first boot, summarised in a root only file, never baked into the image

  • A demo history record seeded on first boot so the sync API is provably live

  • Unattended security upgrades left enabled so the appliance keeps receiving patches

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in the target region

  • Subscription to the Atuin listing on Azure Marketplace

  • A Network Security Group rule allowing TCP 22 (admin) and TCP 80 (the sync API) from the networks that need them

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for personal or small team sync. For a larger team or a bigger history store, use Standard_D2s_v5 or larger and attach a data disk for the PostgreSQL data directory.

Step 1: Deploy from the Azure Portal

Search Atuin in Marketplace, select the cloudimg publisher, and click Create. Configure the Network Security Group to allow TCP 22 for administration and TCP 80 for the sync API from the networks that need them. No inbound database port is required — the bundled PostgreSQL is loopback only.

Step 2: Deploy from the Azure CLI

RG="atuin-prod"; LOCATION="eastus"; VM_NAME="atuin1"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/atuin-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
# Open the admin and sync ports on the VM's NSG:
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1002

Step 3: First boot

On first boot the image starts PostgreSQL on the loopback interface, generates a per VM password for the atuin database role, recreates a clean application database, seeds a per machine Atuin account plus a demo history record, and writes /root/atuin-credentials.txt. This completes within a minute. SSH in as azureuser and read the details:

sudo cat /root/atuin-credentials.txt

The file contains the sync address, the seeded account username, its password and the PostgreSQL password. Keep it safe.

Step 4: Confirm the service is healthy

The Atuin server, nginx and PostgreSQL are all active, the server binds only to the loopback interface, and nginx serves the unauthenticated /healthz endpoint on port 80.

systemctl is-active atuin.service nginx.service postgresql.service
echo '--- atuin and PostgreSQL are loopback only (no public app/db port) ---'
ss -tlnH | grep -E ':8888 |:5432 ' | awk '{print $4}' | sort -u
echo -n '/healthz through nginx :80 -> HTTP '
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz

systemctl reports atuin, nginx and postgresql all active, ss shows the atuin server bound to 127.0.0.1:8888 and PostgreSQL bound to 127.0.0.1:5432 with no public port, and the nginx proxied health endpoint returns HTTP 200, confirming the sync server is healthy and loopback only

Step 5: Open registration is disabled by default

The appliance is single tenant: open_registration is false, so a fresh registration attempt against the sync API is refused with HTTP 400. This is what stops a public deployment from becoming an open sign up service.

echo '--- server.toml: loopback bind + open registration disabled ---'
sudo grep -E '^(host|port|open_registration)' /etc/atuin/server.toml
echo -n 'POST /register (should be refused) -> HTTP '
curl -s -o /dev/null -w '%{http_code}\n' -H 'Content-Type: application/json' \
  -d '{"username":"someone","email":"someone@example.com","password":"changeme12345"}' \
  http://127.0.0.1/register

The server.toml shows host 127.0.0.1, port 8888 and open_registration false, and a POST to the register endpoint returns HTTP 400, confirming that the appliance ships single tenant with open registration disabled

Step 6: The sync API is live — account round-trip

The per machine account seeded on first boot authenticates through the real sync API. Here we log in for a session token, confirm the account with the authenticated /api/v0/me endpoint, store a history record, and read the record count back — the same round-trip the Atuin client performs when it syncs.

U=$(sudo grep '^ATUIN_ACCOUNT_USERNAME=' /root/atuin-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^ATUIN_ACCOUNT_PASSWORD=' /root/atuin-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -s -H 'Content-Type: application/json' -d "{\"username\":\"$U\",\"password\":\"$P\"}" http://127.0.0.1/login | jq -r .session)
echo "Logged in as $U; session token: ${TOKEN:0:12}..."
echo -n 'GET /api/v0/me -> '; curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1/api/v0/me
echo; echo -n 'History records before: '; curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1/sync/count | jq -r .count
curl -s -o /dev/null -H "Authorization: Token $TOKEN" -H 'Content-Type: application/json' \
  -d "[{\"id\":\"guide-$(date +%s%N)\",\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"data\":\"encrypted-history-blob\",\"hostname\":\"my-laptop\"}]" \
  http://127.0.0.1/history
echo -n 'History records after: '; curl -s -H "Authorization: Token $TOKEN" http://127.0.0.1/sync/count | jq -r .count

The seeded account logs in and receives a session token, the authenticated me endpoint returns the account username, and after posting a history record the record count returned by the sync count endpoint increases, proving the sync API is live and persisting encrypted records in PostgreSQL

Step 7: The PostgreSQL backing store

Atuin stores its accounts, sessions and encrypted history and record data in the bundled PostgreSQL 16 database. The schema is created automatically the first time the server starts.

sudo -u postgres psql -d atuin -c '\dt'

psql lists the Atuin tables in the atuin database, including history, users, sessions, records and store, showing the PostgreSQL backing store that holds the encrypted sync data

Step 8: Connect the Atuin client

On each of your own machines, install the Atuin client, point it at this server and log in with the seeded account (or your own, once you enable registration). Generate your encryption key on your first machine and copy it to the others — the key never touches the server. These commands run on your own machine, not on the VM:

# On your own machine (see https://docs.atuin.sh):
atuin login --username cloudimg --password '<password from /root/atuin-credentials.txt>' \
  --sync-address http://<your-vm-address>/
atuin key            # print your encryption key (run on the first machine)
atuin sync           # sync your shell history through your server

Set the sync address permanently in your client config (~/.config/atuin/config.toml):

sync_address = "http://<your-vm-address>/"

Step 9: Invite more people (enable registration)

To let other people register their own accounts, set open_registration = true in /etc/atuin/server.toml on the VM and restart the service. Turn it back off once everyone has registered.

sudo sed -i 's/^open_registration = false/open_registration = true/' /etc/atuin/server.toml
sudo systemctl restart atuin.service
# ... let people run: atuin register -u <name> -e <email> -p <password> --sync-address http://<your-vm-address>/
# then disable it again:
sudo sed -i 's/^open_registration = true/open_registration = false/' /etc/atuin/server.toml
sudo systemctl restart atuin.service

Step 10: Add TLS

The nginx reverse proxy is ready for a certificate. Point a DNS name at the VM, open TCP 443 on the NSG, and install a certificate with your preferred tool (for example certbot with the nginx plugin), then set the sync address on your clients to the https:// URL.

Step 11: Security recommendations

  • Keep the database private. The bundled PostgreSQL is loopback only and no database port is exposed. Never expose port 5432 or 8888 publicly; reach the server only through nginx.

  • Restrict the NSG. Allow TCP 22 from your management network and TCP 80 or 443 only from the networks that sync.

  • Leave open registration off. Enable it only while inviting users, then disable it again (Step 9). Anyone who can register can store encrypted blobs on your server.

  • Use TLS in production. Serve the sync API over HTTPS (Step 10) so client tokens are never sent in clear text.

  • Back up PostgreSQL. Your accounts and encrypted history live in the atuin database. Back it up like any production database and consider an encrypted data disk for the data directory.

  • Keep the OS and Atuin patched. Unattended security upgrades remain enabled on the running VM. Upgrade the Atuin server binary when new releases ship.

Step 12: Support and Licensing

Atuin is developed by the Atuin project and distributed under the MIT license. This cloudimg image bundles the unmodified official Atuin server binary. cloudimg provides the packaging, the secure by default loopback only configuration with open registration disabled, the PostgreSQL backing store and nginx reverse proxy, the first boot credential generation, and 24/7 support with a guaranteed 24 hour response SLA.

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

Deploy on Azure

Find Atuin 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.