SPIRE on Ubuntu 24.04 on Azure User Guide
Overview
This image runs SPIRE, the SPIFFE Runtime Environment. SPIRE is the graduated Cloud Native Computing Foundation project that gives every workload a cryptographically verifiable identity. Instead of distributing shared secrets, API keys or long lived certificates to your services, SPIRE attests what each workload actually is and issues it a short lived identity document, called an SVID, that other services can verify.
The image is delivered as a complete, self contained identity system. Both halves of SPIRE run on this one VM: the SPIRE server, which is the certificate authority for your trust domain and holds the registration entries, and a local SPIRE agent, which attests to the server and serves the SPIFFE Workload API over a unix socket. Both services are enabled and start on boot, so by the time you connect over SSH the system is already issuing identities.
This is a headless product. There is no web interface. SPIRE is operated through the spire-server and spire-agent command line tools, a pair of unix sockets, and plain health endpoints.
Every VM generates its own trust domain and its own certificate authority on first boot. The signing keys are created locally on your machine. No certificate authority material is shared between instances and none ships in the image, so identities issued on your VM are valid only inside your own trust domain.
What's included:
- SPIRE 1.15.2 (server and agent), Apache 2.0 licensed
- A per VM trust domain and self signed trust domain certificate authority, generated on first boot
- A demonstration workload identity and registration entry
- An identity round trip self test at
/usr/local/sbin/spire-identity-roundtrip.sh - A dedicated 16 GB Azure data disk mounted at
/var/lib/spire - 24/7 cloudimg support
Key facts:
| Item | Value |
|---|---|
| Platform | Microsoft Azure |
| Default user | azureuser |
| Server node API | 127.0.0.1:8081 (loopback only) |
| Server health | 127.0.0.1:8080 |
| Agent health | 127.0.0.1:8082 |
| Workload API socket | /run/spire/agent/public/api.sock |
| Server admin socket | /run/spire/server/private/api.sock |
| Data disk | /var/lib/spire |
Prerequisites
Before you deploy this image you need:
- A Microsoft Azure subscription where you can create virtual machines
- Permissions to create VMs, virtual networks, network security groups and public IP addresses
- An SSH key pair for access to the VM (the Azure CLI can generate one)
- A virtual network and subnet in the target region, with a network security group allowing inbound port 22 from your management network
- The Azure CLI installed locally if you plan to deploy from the command line
Recommended VM size: Standard_B2s or larger.
Step 1: Launch the VM from the Azure Marketplace
Sign in to the Azure portal, open Create a resource, and search the Marketplace for SPIRE by cloudimg. Select the cloudimg listing and choose Create.
On the Basics tab pick your subscription and resource group, a region, and a VM size of Standard_B2s or larger. Under Administrator account select SSH public key, set the username to azureuser, and supply your public key. Under Inbound port rules allow SSH (22) only. Complete the Networking tab with your virtual network and subnet, then select Review + create and Create.
First boot initialisation generates this VM's own trust domain and certificate authority, registers the demonstration workload, and takes a short moment after the VM state becomes Running.
Choosing your own trust domain
If you want to choose the trust domain name yourself rather than letting the VM derive one, write a single line containing it to /etc/cloudimg/spire-trust-domain before first boot, for example through cloud init user data:
#cloud-config
write_files:
- path: /etc/cloudimg/spire-trust-domain
content: |
example.org
If you do not supply one, the VM derives a unique trust domain from its hostname, so two cloudimg VMs never share a trust domain by accident.
Step 2: Launch the VM from the Azure CLI
The following block launches a VM from the cloudimg SPIRE image into an existing resource group. Replace the placeholders with your own values. The image reference comes from the Marketplace listing's Usage information.
az vm create \
--resource-group <your-resource-group> \
--name spire \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_rsa.pub \
--public-ip-sku Standard
When the VM reaches the Running state, note its public IP address from the portal or with az vm list-ip-addresses.
Step 3: Connect to Your VM
Connect over SSH as azureuser using your key pair.
ssh azureuser@<vm-ip>
Step 4: Confirm the SPIRE Services and Versions
Check that both halves of SPIRE are installed and running.
spire-server --version
spire-agent --version
sudo systemctl is-active spire-server.service spire-agent.service
Expected output:
1.15.2
1.15.2
active
active

Step 5: Check the Health Endpoints
Both the server and the agent expose plain HTTP health endpoints on the loopback address. They are the quickest way to confirm the identity system is up.
curl -fsS http://127.0.0.1:8080/ready
echo
curl -fsS http://127.0.0.1:8082/ready
echo
Expected output:
{"catalog.datastore":{},"server":{},"server.ca":{},"server.ca.rotator":{}}
{"agent":{}}
Confirm that SPIRE listens on the loopback address only. The server's node API on 8081 is deliberately not reachable from outside the VM in the shipped image.
sudo ss -tln | grep -E ':(8080|8081|8082) '
Expected output:
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8081 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8082 0.0.0.0:*

Step 6: Read Your VM's Identity Details
First boot writes a root only summary of this VM's identity configuration. It records your trust domain, the identity paths in use and the fingerprint of your certificate authority. It contains no private key material.
sudo cat /root/spire-credentials.txt
The file records values like these. Your trust domain and fingerprint will be unique to your VM.
spire.trust.domain=<your-vm>.spire.cloudimg.internal
spire.agent.id=spiffe://<your-vm>.spire.cloudimg.internal/agent/cloudimg-local
spire.workload.demo.id=spiffe://<your-vm>.spire.cloudimg.internal/workload/demo
spire.workload.demo.user=spire-workload
spire.ca.fingerprint.sha256=CC:55:70:68:24:2A:50:EF:...
spire.server.socket=/run/spire/server/private/api.sock
spire.agent.socket=/run/spire/agent/public/api.sock
spire.server.bind=127.0.0.1:8081
The single use join token that bootstrapped the local agent was generated on this VM, consumed immediately, and deleted. It is not recorded in this file.
Step 7: Prove a Real Identity Round Trip
A running process and an open socket prove nothing about an identity system. The image ships a self test that proves the whole path end to end, and it is the fastest way to confirm your deployment genuinely works.
The self test pulls the trust bundle directly from the server, has the demonstration workload fetch an SVID over the Workload API into a fresh directory, checks the SVID carries your trust domain, verifies the certificate chain against the server supplied bundle, confirms an unrelated certificate authority is correctly rejected, and re confirms the positive case afterwards.
sudo /usr/local/sbin/spire-identity-roundtrip.sh
Expected output:
OK trust_domain=<your-vm>.spire.cloudimg.internal workload=spiffe://<your-vm>.spire.cloudimg.internal/workload/demo ca_sha256=CC:55:70:68:24:2A:50:EF:...
Any other output begins with FAIL and names the step that did not hold.

Step 8: Inspect Attested Agents and Registration Entries
The server tracks which agents have attested and which workloads may receive which identities.
sudo spire-server agent list -socketPath /run/spire/server/private/api.sock
Expected output:
Found 1 attested agent:
SPIFFE ID : spiffe://<your-vm>.spire.cloudimg.internal/spire/agent/join_token/fc29a008-8ea7-4bcd-b417-a1904a16b416
Attestation type : join_token
Expiration time : 2026-07-19 17:03:13 +0000 UTC
Serial number : 167376281661483824124114967622690597522
Can re-attest : false
Agent version : 1.15.2
Now list the registration entries.
sudo spire-server entry show -socketPath /run/spire/server/private/api.sock
Two entries exist on a fresh VM. The first is the delegated agent identity, .../agent/cloudimg-local, which is the parent your own workload entries hang off. The second is the demonstration workload, selected by the unix user id it runs as.
Entry ID : 9641806b-147c-490c-8fc6-ad1d259dfef8
SPIFFE ID : spiffe://<your-vm>.spire.cloudimg.internal/workload/demo
Parent ID : spiffe://<your-vm>.spire.cloudimg.internal/agent/cloudimg-local
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : unix:uid:997

Step 9: Fetch and Inspect an SVID
Fetch an identity document as the demonstration workload and inspect what SPIRE actually issued. The workload identity is granted on the basis of the unix user the process runs as, so the fetch is run with sudo -u spire-workload.
The SPIFFE identity is carried in the certificate's URI Subject Alternative Name, which is what makes it verifiable by any SPIFFE aware peer.
WD=$(mktemp -d); chmod 777 "$WD"
sudo -u spire-workload spire-agent api fetch x509 \
-socketPath /run/spire/agent/public/api.sock -write "$WD/"
openssl x509 -in "$WD/svid.0.pem" -noout -text | grep -A1 "Subject Alternative Name"
openssl x509 -in "$WD/svid.0.pem" -noout -issuer -dates
openssl verify -CAfile "$WD/bundle.0.pem" "$WD/svid.0.pem"
rm -rf "$WD"
Expected output:
Received 1 svid after 1.839615ms
SPIFFE ID: spiffe://<your-vm>.spire.cloudimg.internal/workload/demo
SVID Valid After: 2026-07-19 16:04:08 +0000 UTC
SVID Valid Until: 2026-07-19 17:04:18 +0000 UTC
CA #1 Valid After: 2026-07-19 16:03:02 +0000 UTC
CA #1 Valid Until: 2026-07-26 16:03:12 +0000 UTC
Writing SVID #0 to file /tmp/tmp.uj6oEt8zMe/svid.0.pem.
Writing key #0 to file /tmp/tmp.uj6oEt8zMe/svid.0.key.
Writing bundle #0 to file /tmp/tmp.uj6oEt8zMe/bundle.0.pem.
X509v3 Subject Alternative Name:
URI:spiffe://<your-vm>.spire.cloudimg.internal/workload/demo
issuer=C = GB, O = cloudimg, CN = cloudimg SPIRE CA (<your-vm>.spire.cloudimg.internal), serialNumber = 97931985402370782961606615520983568838
notBefore=Jul 19 16:04:08 2026 GMT
notAfter=Jul 19 17:04:18 2026 GMT
/tmp/tmp.uj6oEt8zMe/svid.0.pem: OK
Note the one hour lifetime. Short lived identities are the point of SPIFFE: the agent renews them automatically well before expiry, so there is no long lived credential to steal or rotate by hand.
Step 10: Register Your Own Workload
Register one of your own services by selecting on the unix user id it runs as. The trust domain is read from the credentials file so the command can be copied as is. The block is safe to re run: SPIRE rejects a duplicate registration with AlreadyExists, so it checks for the entry before creating it.
TD=$(sudo grep '^spire.trust.domain=' /root/spire-credentials.txt | cut -d= -f2-)
SOCK=/run/spire/server/private/api.sock
MYID="spiffe://${TD}/workload/my-service"
EXISTING=$(sudo spire-server entry show -socketPath "$SOCK" -spiffeID "$MYID" 2>/dev/null || true)
case "$EXISTING" in
*"Entry ID"*)
echo "Entry already exists for $MYID"
printf '%s\n' "$EXISTING"
;;
*)
sudo spire-server entry create -socketPath "$SOCK" \
-parentID "spiffe://${TD}/agent/cloudimg-local" \
-spiffeID "$MYID" \
-selector "unix:uid:$(id -u www-data)"
;;
esac
Expected output:
Entry ID : 3f9d1a0e-e37e-4192-862b-36822ee9675f
SPIFFE ID : spiffe://<your-vm>.spire.cloudimg.internal/workload/my-service
Parent ID : spiffe://<your-vm>.spire.cloudimg.internal/agent/cloudimg-local
Revision : 0
X509-SVID TTL : default
JWT-SVID TTL : default
Selector : unix:uid:33
Any process running as that user can now fetch its identity from the Workload API socket, with no secret to distribute. Selectors are not limited to the unix user id: unix:gid, unix:path and other selectors let you describe a workload as precisely as you need.
Step 11: Where the Identity Material Lives
The trust domain certificate authority signing keys, the registration datastore and the agent's own node identity all live on a dedicated Azure data disk mounted at /var/lib/spire, kept off the operating system disk so you can resize and snapshot it independently.
df -h /var/lib/spire
sudo ls -l /var/lib/spire/server /var/lib/spire/agent
Expected output:
Filesystem Size Used Avail Use% Mounted on
/dev/sdc 16G 304K 15G 1% /var/lib/spire
/var/lib/spire/agent:
-rw------- 1 root root 2944 Jul 19 16:04 agent-data.json
-rw-r--r-- 1 root root 1062 Jul 19 16:03 bootstrap-bundle.crt
-rw------- 1 root root 222 Jul 19 16:03 keys.json
/var/lib/spire/server:
-rw-r----- 1 spire spire 143360 Jul 19 16:03 datastore.sqlite3
-rw------- 1 spire spire 425 Jul 19 16:03 keys.json
server/keys.json holds your trust domain's certificate authority signing keys. It is readable only by the spire service user and it was generated on this VM. Back up this disk if you need your trust domain to survive the VM.
Step 12: Open the Server Port for Agents on Other Hosts
The shipped image runs a single node identity system: the server listens on 127.0.0.1:8081 only, so no unauthenticated identity API is exposed to the network. To attest SPIRE agents running on other hosts, bind the server to a routable address and open the port to those hosts only.
Edit /etc/spire/server/server.conf and change bind_address from 127.0.0.1 to 0.0.0.0, then restart the server:
server {
bind_address = "0.0.0.0"
bind_port = "8081"
...
}
Then allow inbound 8081 from the hosts that need it, never from the whole internet:
az network nsg rule create \
--resource-group <your-resource-group> \
--nsg-name <your-nsg> \
--name allow-spire-server \
--priority 1010 \
--protocol Tcp --destination-port-ranges 8081 \
--source-address-prefixes <your-workload-cidr> \
--access Allow
Each remote agent needs its own join token, generated on the server, plus a copy of your trust bundle:
TD=$(sudo grep '^spire.trust.domain=' /root/spire-credentials.txt | cut -d= -f2-)
sudo spire-server token generate \
-socketPath /run/spire/server/private/api.sock \
-spiffeID "spiffe://${TD}/agent/remote-host-1" -ttl 600
sudo spire-server bundle show -socketPath /run/spire/server/private/api.sock
Join tokens are single use and short lived. For production fleets, consider a node attestor suited to your platform rather than issuing tokens by hand. The Azure managed identity node attestors require Azure identity configuration you would set up yourself, and are deliberately not enabled in this image so that it works standalone out of the box.
Server Components
| Component | Version | Install path |
|---|---|---|
| SPIRE Server | 1.15.2 | /opt/spire/bin/spire-server |
| SPIRE Agent | 1.15.2 | /opt/spire/bin/spire-agent |
| Server configuration | — | /etc/spire/server/server.conf |
| Agent configuration | — | /etc/spire/agent/agent.conf |
| Identity round trip self test | — | /usr/local/sbin/spire-identity-roundtrip.sh |
Filesystem Layout
| Mount point | Size | Purpose |
|---|---|---|
/ |
29 GB | Root filesystem |
/boot/efi |
105 MB | UEFI boot partition (Gen2 Hyper V) |
/var/lib/spire |
16 GB | Dedicated data disk holding the certificate authority keys, datastore and agent identity |
/mnt |
varies | Azure temporary resource disk |
Key directories:
| Path | Purpose |
|---|---|
/opt/spire/bin |
SPIRE server and agent binaries |
/etc/spire |
Server and agent configuration |
/var/lib/spire/server |
Certificate authority signing keys and the registration datastore |
/var/lib/spire/agent |
The agent's node identity and bootstrap trust bundle |
/run/spire/server/private |
Server admin socket (mode 0700) |
/run/spire/agent/public |
Workload API socket |
Managing the Services
sudo systemctl status spire-server.service
sudo systemctl status spire-agent.service
Use sudo systemctl restart spire-server.service and sudo systemctl restart spire-agent.service to restart them, and journalctl -u spire-server.service -n 50 or journalctl -u spire-agent.service -n 50 to read their logs. The agent persists its node identity, so it re attests from stored state across a restart without needing a new join token.
Scripts and Log Files
| Path | Purpose |
|---|---|
/usr/local/sbin/spire-firstboot.sh |
First boot initialisation: generates the per VM trust domain and certificate authority |
/usr/local/sbin/spire-identity-roundtrip.sh |
Identity round trip self test |
/usr/local/sbin/spire-write-config.sh |
Renders the server and agent configuration for a given trust domain |
/usr/local/sbin/spire-agent-start.sh |
Agent start wrapper that presents the single use join token only when needed |
/var/lib/cloudimg/spire-firstboot.done |
First boot sentinel |
/stage/scripts/initial_boot_update.sh |
Standard cloudimg first boot update helper |
On Startup
On the first boot of a new VM, spire-firstboot.service generates this VM's trust domain and a brand new certificate authority, starts the server, exports the trust bundle, issues a single use join token to the local agent, registers the demonstration workload and writes /root/spire-credentials.txt. It then creates the sentinel file /var/lib/cloudimg/spire-firstboot.done so it never runs again.
The standard cloudimg initial_boot_update.sh helper also runs on first boot from a root crontab entry. To disable it, edit the root crontab with sudo crontab -e and remove the @reboot line.
Troubleshooting
The round trip self test reports FAIL
Run it again and read the named step. FAIL the demo workload could not fetch an X.509 SVID usually means the agent is not running or has not attested. Check sudo systemctl status spire-agent.service and journalctl -u spire-agent.service -n 50.
A workload gets no identity
The Workload API only issues an identity to a process that matches a registration entry's selectors. Confirm the entry exists with sudo spire-server entry show -socketPath /run/spire/server/private/api.sock and confirm the selector matches the user the process actually runs as, with id -u <user>.
The agent will not attest after a restart
The agent stores its node identity in /var/lib/spire/agent/agent-data.json. If that file is removed the agent needs a fresh join token, because join tokens are single use. Generate one with sudo spire-server token generate and restart the agent.
First boot did not complete
Check sudo systemctl status spire-firstboot.service and journalctl -u spire-firstboot.service. The sentinel at /var/lib/cloudimg/spire-firstboot.done is only created on success.
The server will not start after a configuration change
SPIRE validates its configuration at startup. Read journalctl -u spire-server.service -n 50 for the parse error. Note that changing the trust domain of an existing server invalidates every identity already issued, because the certificate authority is scoped to the trust domain.
Security Recommendations
- Keep the server on loopback unless you need remote agents. The shipped default exposes no identity API to the network.
- Restrict port 8081 to your workload subnet if you do open it, never to the whole internet.
- Protect the data disk.
/var/lib/spire/server/keys.jsonholds your trust domain's certificate authority signing keys. Anyone with those keys can mint identities in your trust domain. - Keep SVID lifetimes short. The default is one hour, which limits the value of a stolen certificate. The agent renews automatically.
- Prefer precise selectors. A broad selector grants an identity to more processes than you intended.
- Restrict SSH to your management network with a network security group rule.
- Keep the VM patched with
sudo apt-get update && sudo apt-get upgrade.
Support
For assistance, contact cloudimg support:
- Email: support@cloudimg.co.uk
- Response Time: 24/7 with a one hour average response time for critical issues
- Website: https://www.cloudimg.co.uk