asciinema server on Ubuntu 24.04 on Azure User Guide
Overview
asciinema server is the server side of the asciinema ecosystem. You record a terminal session with the asciinema CLI, upload it to your own server, and get back a link that replays the session in a real terminal player. Because a recording is stored as timed text rather than video, the playback keeps its text selectable and copyable, loads instantly, stays small, and can be searched by its actual terminal content.
Around that core the server gives you a browsable library with full text search, per recording visibility (private, unlisted or public), secret sharing links, an embeddable player, downloadable .cast and plain text transcripts, live terminal streaming, and an administration panel.
This cloudimg image runs asciinema server 20260626 from the official container image, pinned to an immutable digest, with PostgreSQL 16 co-hosted on the same VM and its data directory on a dedicated Azure data disk, fronted by nginx on port 80. Every VM generates its own session signing key, database password, administrator account and CLI upload token on first boot, and ships with public sign up switched off. Backed by 24/7 cloudimg support.
What is included:
- asciinema server
20260626(containerghcr.io/asciinema/asciinema-server, Apache-2.0), pinned to an immutablesha256digest so the shipped version never drifts - PostgreSQL 16, with its cluster data directory on a dedicated 32 GiB Azure data disk mounted at
/var/lib/asciinema - Recordings (
/var/lib/asciinema/uploads) and the derived render cache (/var/lib/asciinema/cache) on that same dedicated disk - nginx on
:80reverse proxying to the container, which publishes only to127.0.0.1:4000, with WebSocket upgrade headers and an unauthenticated/healthzendpoint for load balancer and uptime checks - A per-VM
SECRET_KEY_BASE, PostgreSQL password, administrator account and pre-authorised CLI upload token, all generated on first boot and recorded in a root-only file - Public sign up disabled and anonymous uploads disabled by default, with the administrator account claimed during first boot
asciinema-login-url, a root-only helper that mints a fresh sign in link on demand, so no mail server is neededdocker.service,postgresql.service,nginx.service,asciinema.serviceandasciinema-firstboot.serviceas systemd units- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is the recommended size and is comfortable for a personal or team instance; move up to a Standard_B2ms or Standard_B4ms for a large library or many concurrent viewers.
Network security group inbound rules: allow 22/tcp from your management network and 80/tcp from wherever you will use asciinema. The server speaks plain HTTP on port 80 — for anything reachable from the internet, terminate TLS in front of it (an Azure Application Gateway or Front Door, or your own certificate on the VM's nginx) and use a real hostname.
PostgreSQL listens on the loopback interface and on the Docker bridge gateway so the application container can reach it. It is never bound to the VM's routable address, and no NSG rule should ever open 5432.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal choose Create a resource, search for asciinema server on Ubuntu 24.04 LTS by cloudimg, and select Create. Pick your subscription, resource group and region, set the size to Standard_B2s, choose SSH public key authentication with username azureuser, and allow inbound 22 and 80. The image already carries the dedicated data disk definition, so the recordings and database volume is created and mounted for you.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-resource-group \
--name my-asciinema \
--image cloudimg:asciinema:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# then open the port asciinema serves on
az vm open-port --resource-group my-resource-group --name my-asciinema --port 80
First boot takes about a minute: the VM generates its own secrets, runs the database migrations, claims the administrator account and only then opens port 80.
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
All five units ship enabled. asciinema.service and postgresql.service are oneshot wrappers, so they report active (exited) once they have brought their payload up — that is the healthy state.
systemctl is-active docker.service postgresql.service nginx.service asciinema.service
active
active
active
active
The application itself runs as a single container, published to the loopback interface only:
docker ps --filter 'name=^asciinema$' --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
NAMES STATUS PORTS
asciinema Up 3 minutes 127.0.0.1:4000->4000/tcp
Confirm the running release is the pinned version rather than a development build:
docker exec asciinema /opt/app/bin/asciinema rpc 'IO.puts(Application.get_env(:asciinema, :version))'
20260626
nginx exposes an unauthenticated health endpoint for load balancers and uptime checks:
curl -s http://127.0.0.1/healthz
ok

Step 5 - Sign in to the web interface
asciinema server authenticates with passwordless sign in links — there is no password to type. This image ships without a mail relay, so instead of emailing you a link it gives you a root-only helper that mints one on demand:
asciinema-login-url
http://13.68.186.216/session/new?t=SFMyNTY.g2gDaAJhAXcDbmlsbgYA...
Open that URL in your browser. It shows a short confirmation page — click Log in on it and you are signed in as the instance administrator. Links are valid for 60 minutes (an upstream limit), so run the helper again whenever you need a fresh one. Pass a username or email to mint a link for a different account.
Your per-VM details, including the administrator username and the pre-authorised CLI upload token, are in a root-only file:
cat /root/asciinema-credentials.txt
# asciinema server - generated on first boot by asciinema-firstboot.service
# These values are unique to this VM. Store them somewhere safe.
asciinema.url=http://13.68.186.216/
asciinema.admin.username=cloudimgadmin
asciinema.admin.email=admin@cloudimg.local
asciinema.cli.token=3f9c1a2e-7b4d-4c8e-9a15-2d6e8f0b3c47
asciinema.db.password=...
asciinema.login.url=...

Once signed in you land on the home page. It lists public recordings — a brand new instance is empty here, and it fills up as you upload. The screenshot below is the same instance after the uploads in the next step:

Uploads default to unlisted visibility, which means reachable by link but deliberately not shown in public listings — so if the home page looks empty after uploading, that is the privacy default doing its job, not a fault. Change a recording's visibility from its own page.
Step 6 - Record and upload your first session
Install the asciinema CLI on the machine you want to record — your laptop or a server, not necessarily this VM:
# macOS
brew install asciinema
# Debian / Ubuntu
sudo apt install asciinema
This image disables anonymous uploads, so the CLI has to identify itself. Every VM generates a pre-authorised upload token bound to its administrator account, so you can simply write that token in as your CLI install id:
mkdir -p ~/.config/asciinema
echo <ASCIINEMA_CLI_TOKEN> > ~/.config/asciinema/install-id
Then record and upload against your own server:
asciinema rec --server-url http://<vm-public-ip>/ demo.cast
# ... run some commands, then press Ctrl-D to finish ...
asciinema upload --server-url http://<vm-public-ip>/ demo.cast
The server replies with the share link, which is what makes asciinema useful — you hand that URL to someone and they replay your terminal in a browser:
View the recording at:
http://13.68.186.216/a/OkIgeVCebJ8HbiBE
You can exercise the same HTTP API the CLI uses directly from the VM. This uploads a tiny two-frame recording using the per-VM token and prints the URL the server hands back:
cat > /tmp/guide-demo.cast <<'CAST'
{"version": 2, "width": 80, "height": 24, "timestamp": 1700000000, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}}
[0.1, "o", "$ echo hello from asciinema\r\n"]
[0.4, "o", "hello from asciinema\r\n"]
CAST
curl -s -u "<ASCIINEMA_ADMIN_USERNAME>:<ASCIINEMA_CLI_TOKEN>" \
-F "asciicast=@/tmp/guide-demo.cast" http://127.0.0.1/api/asciicasts | jq -r '.url'
http://13.68.186.216/a/OkIgeVCebJ8HbiBE
Note that the link carries your VM's public address, not the address the request arrived on. The server builds share links from its own configured URL_HOST, which first boot sets to this VM's public IP, so a link is usable by whoever you send it to. If you serve the instance on your own domain, update URL_HOST as shown further down.
Open the returned link and the recording replays in the embedded player:

Your own recordings are all listed on your profile at /~<username>, whatever their visibility, with titles, durations and a visibility badge:

Step 7 - Confirm the security posture
Upstream asciinema server grants administrator rights to the first account that registers and leaves public sign up open, which would mean the first stranger to find your server could claim it. This image closes that before port 80 ever opens: sign up is disabled from the moment the server starts, and first boot claims the administrator account itself.
Exactly one administrator exists, and it is your per-VM account:
DBP=$(grep '^asciinema.db.password=' /root/asciinema-credentials.txt | cut -d= -f2-)
PGPASSWORD="$DBP" psql -w -h 127.0.0.1 -U asciinema -d asciinema -tAc \
"SELECT count(*) FILTER (WHERE is_admin), string_agg(username, ',') FROM users;"
1|cloudimgadmin
Public sign up and anonymous upload are both off, and the upstream admin endpoint is explicitly rebound:
grep -E '^(SIGN_UP_DISABLED|UPLOAD_AUTH_REQUIRED|ADMIN_BIND_ALL|ADMIN_PANEL_ON_MAIN_ENDPOINT)=' /etc/asciinema/asciinema.env
ADMIN_BIND_ALL=0
ADMIN_PANEL_ON_MAIN_ENDPOINT=1
SIGN_UP_DISABLED=true
UPLOAD_AUTH_REQUIRED=1
An unrecognised CLI token is refused rather than silently creating a throwaway account:
printf '%s\n' '{"version": 2, "width": 80, "height": 24, "timestamp": 1700000000, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}}' > /tmp/anon-probe.cast
printf '%s\n' '[0.1, "o", "anonymous probe\r\n"]' >> /tmp/anon-probe.cast
curl -s -o /dev/null -w '%{http_code}\n' -u "nobody:$(uuidgen)" \
-F "asciicast=@/tmp/anon-probe.cast" http://127.0.0.1/api/asciicasts
401
Only nginx is reachable from the network. The application port, the administration port and PostgreSQL are all bound away from the VM's routable address:
ss -lntH | awk '{print $4}' | grep -E ':(80|4000|4002|5432)$' | sort
0.0.0.0:80
127.0.0.1:4000
127.0.0.1:5432
172.17.0.1:5432
[::]:80
172.17.0.1 is the Docker bridge gateway — a host-local interface reachable only from the application container, never from the network. Port 4002 does not appear at all: that is asciinema's administration endpoint, which performs no authentication of its own, so this image never publishes it. The administration panel is served at /admin behind an administrator session instead.

Signed in as the administrator you can reach the panel at /admin:

Step 8 - Recordings and the database on the dedicated disk
Everything durable lives on the dedicated data disk, so it survives a resize and is re-provisioned with every VM:
df -h /var/lib/asciinema | tail -1
/dev/sdc 32G 59M 30G 1% /var/lib/asciinema
sudo -u postgres psql -tAc 'SHOW data_directory'
ls -1 /var/lib/asciinema
/var/lib/asciinema/pgdata/16/main
cache
lost+found
pgdata
uploads
Recordings land under uploads/, the PostgreSQL cluster under pgdata/, and regenerable preview renders under cache/.

Adding more people to your instance
Public sign up ships disabled, so nobody can register on your instance without you. To invite people, either add them from the administration panel at /admin, or re-open public sign up:
# This is the setting as shipped:
grep '^SIGN_UP_DISABLED=' /etc/asciinema/asciinema.env
# To open public sign up, flip it and restart asciinema:
# sudo sed -i 's/^SIGN_UP_DISABLED=true$/SIGN_UP_DISABLED=false/' /etc/asciinema/asciinema.env
# sudo systemctl restart asciinema
SIGN_UP_DISABLED=true
Without a mail relay configured, a new user cannot receive their own sign in link by email — mint it for them with sudo asciinema-login-url <their-username-or-email>. To enable real email delivery, set SMTP_HOST (plus SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD as your relay requires) in /etc/asciinema/asciinema.env and restart.
To let unregistered CLIs upload anonymously, set UPLOAD_AUTH_REQUIRED=0 in the same file and restart. Consider pairing that with UNCLAIMED_RECORDING_TTL="7,30" so unclaimed recordings are hidden after 7 days and deleted after 30.
Serving asciinema on your own domain
Share links, sign in links and the live updating pages are all built from URL_HOST, so it must match the address people actually use. First boot sets it to this VM's public IP. To move to your own hostname, update it and restart:
# As shipped, pointing at this VM's public address:
grep -E '^(URL_HOST|URL_SCHEME|URL_PORT)=' /etc/asciinema/asciinema.env
# For your own domain behind TLS, set:
# URL_HOST=asciinema.example.com
# URL_SCHEME=https
# URL_PORT=443
# then: sudo systemctl restart asciinema
URL_HOST=13.68.186.216
URL_SCHEME=http
URL_PORT=80
This matters more than it looks. Beyond the share links, the live updating panels on the home, explore and stream pages open a WebSocket that upstream checks against URL_HOST, so a mismatched value leaves those panels silently unable to connect even though every page still returns HTTP 200. Recording playback is server rendered and is unaffected either way.
Terminate TLS on an Azure Application Gateway or Front Door, or add your own certificate to the VM's nginx at /etc/nginx/sites-available/cloudimg-asciinema.
Maintenance
The image ships fully patched with unattended security upgrades enabled. To upgrade asciinema server itself, edit the pinned image digest in /etc/asciinema/compose.yaml and restart — database migrations run automatically on start:
grep -E '^\s+image:' /etc/asciinema/compose.yaml
image: ghcr.io/asciinema/asciinema-server@sha256:0474b2186be46b23f5e16dbd4319727dbb68f27a303705fbe632821bf524b313
Back up the database and the uploads directory together — a recording's row and its file are two halves of one object:
sudo -u postgres pg_dump asciinema > /var/lib/asciinema/asciinema-$(date +%F).sql
sudo tar czf /var/lib/asciinema/uploads-$(date +%F).tar.gz -C /var/lib/asciinema uploads
Useful logs:
journalctl -u asciinema-firstboot.service --no-pager | tail -5
Support
cloudimg images come with 24/7 support. Contact support@cloudimg.co.uk with the VM name, region and the output of journalctl -u asciinema.service --no-pager | tail -50.
Upstream project documentation: docs.asciinema.org. asciinema server is free and open source software under the Apache License 2.0; the cloudimg charge covers packaging, security patching, image maintenance and support.