Developer Tools AWS

asciinema server 20260626 on AWS User Guide

| Product: asciinema server 20260626 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the asciinema server 20260626 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

asciinema server is the self hosted half of the asciinema project. Your team records a terminal session with the asciinema command line tool, uploads it, and gets back a link that replays it in a real terminal player: text you can select and copy, not a video. Recordings are small, searchable down to their content, and can be embedded, shared by secret link or made public. The server also relays live terminal streams.

What the cloudimg image adds:

  • Built from source with security patch releases. The image runs asciinema server 20260626 (the upstream release, tag v20260626) built from source with security patch releases of plug 1.19.3, phoenix 1.7.24, cowlib 2.16.1, cowboy 2.15.0, html_sanitize_ex 1.4.5. The upstream 20260626 container pins older versions of those libraries that carry denial of service advisories. The container's Alpine packages are upgraded at build time.
  • HTTPS from the first boot. nginx serves the server on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS, and requests for host names the instance does not serve are dropped.
  • No open sign up, one administrator. Upstream makes the first account that registers the administrator and leaves sign up open. Here sign up is closed before anything listens, and first boot creates the one administrator, cloudimgadmin, plus a command line upload token. Anonymous uploads are refused.
  • Secrets unique to your instance. The session signing key, the Erlang distribution cookie, the upload token and the TLS key are generated on first boot. None is baked into the image.
  • A database with no password. PostgreSQL 16 is created on first boot, listens on no TCP port, and the app connects over the local socket with peer authentication, so there is no database password to leak or rotate.
  • Hardened defaults. The app runs as an unprivileged user in a read only container on 127.0.0.1:4000 behind nginx. Cross origin form posts and WebSocket connections (including the live stream sockets) are refused, the real client address comes from the connection, and no service can reach the instance metadata service. The AMI requires IMDSv2.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: the cloudimg asciinema server 20260626 container image (cloudimg/asciinema-server:20260626-cloudimg), Docker Engine, PostgreSQL 16, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. Recordings, the render cache and the database live on a separate 32 GB data volume mounted at /var/lib/asciinema.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (the web interface, the API and live streams) and TCP 80 (redirects to HTTPS) from your own networks only.
  • The asciinema command line tool (3.x) on the machines you record from.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick asciinema server 20260626 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above. Keep the data volume in the launch template; it holds your recordings and database.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=asciinema}]'

Step 2: Connect to your instance

Version SSH user
asciinema server 20260626 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot creates the database, generates the secrets and the certificate, and creates the administrator. Then the app and nginx start:

systemctl is-active asciinema-firstboot asciinema nginx postgresql@16-main docker
sudo test -f /var/lib/cloudimg/asciinema-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on public addresses. The app listens on loopback, and PostgreSQL has no TCP listener at all:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | grep -v '127.0.0.5' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:4000 users:(("docker-proxy"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Find your URL and administrator

sudo grep -E '^asciinema\.(url|admin)' /root/asciinema-credentials.txt

The file is readable by root only. It also holds your command line upload token (asciinema.cli.token); keep it secret:

asciinema.url=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
asciinema.admin.username=cloudimgadmin
asciinema.admin.email=admin@cloudimg.invalid

The URL uses the instance's public DNS name from EC2 instance metadata. Other names of the instance (its IP addresses) redirect to it. To use your own DNS name, see Step 11.

Step 5: Sign in

asciinema has no passwords: it signs people in with one time links sent by e-mail. This image sends no mail, so mint a sign in link on the instance:

sudo asciinema-login-url 2>/dev/null | sed -E 's/t=.*/t=<one-time-token>/'
https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/session/new?t=<one-time-token>

Run sudo asciinema-login-url without the filter to see the full link, open it in your browser and select Log in. The certificate is self signed, so your browser asks you to confirm it the first time (or use your own certificate, Step 11). A link is valid for 60 minutes; run the helper again for a new one. Then open your account settings and replace admin@cloudimg.invalid with your real e-mail address.

The home page lists recent public recordings:

The asciinema server home page listing three recent recordings with their terminal previews, titles, durations and author

Your profile shows your live streams and recordings, with the exact command to start a live stream:

The cloudimgadmin profile page with the live streams section showing the asciinema stream command, and the recent recordings section

Step 6: Record from your own machine

Install the asciinema CLI 3.x on your workstation (macOS: brew install asciinema; other systems: see the asciinema documentation), point it at your server (its public IP address or DNS name), and authorise it with the upload token from /root/asciinema-credentials.txt. The CLI identifies itself with an install id; using the instance's pre-authorised token as that id links it to the administrator account:

export ASCIINEMA_SERVER_URL=https://<public-ip>/   # or your DNS name (Step 11)
mkdir -p ~/.config/asciinema && printf '%s\n' '<your-cli-token>' > ~/.config/asciinema/install-id
asciinema rec demo.cast
asciinema upload demo.cast

asciinema upload prints the share link for the recording. If you use the self signed certificate, add it to your workstation's trust store first (or use your own certificate, Step 11). To link another machine or person, run asciinema auth there and open the link it prints while signed in.

Step 7: Upload from the command line

The API is the same one the CLI uses. This uploads a short recording from the instance itself and prints the share link it returns. The token is read inside the root shell and passed to curl on standard input, so it never appears in a process list or log:

sudo bash -c '
  F=/root/asciinema-credentials.txt
  H=$(sed -n "s#^asciinema.url=https://\(.*\)/#\1#p" $F); U=$(sed -n "s/^asciinema.admin.username=//p" $F); T=$(sed -n "s/^asciinema.cli.token=//p" $F)
  printf "%s\n" "{\"version\": 2, \"width\": 80, \"height\": 24}" "[0.5, \"o\", \"hello from cloudimg\\r\\n\"]" > /run/demo.cast
  printf "user = \"%s:%s\"\n" "$U" "$T" | curl -sk -K - --resolve "$H:443:127.0.0.1" -F asciicast=@/run/demo.cast "https://$H/api/asciicasts" | jq -r .url | sed -E "s#/a/[A-Za-z0-9]+#/a/<id>#"
  rm -f /run/demo.cast
'
https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/a/<id>

New recordings are unlisted (reachable by their secret link only) until you make them public or private on the recording's page.

Step 8: Play, share and search

Open a share link to replay the recording in the player. Pause, seek and copy text straight out of the terminal; Share gives you the link and embed code, and Download gives you the .cast file:

A recording titled Rolling out the API service replaying in the asciinema player, with Share, Download and settings buttons below it

Search covers titles, descriptions and the text shown in the terminal:

Search results for migration showing the Database migration dry run recording

Step 9: Stream a terminal live

With the CLI linked to your account (Step 6), start a live stream; viewers watch it at the stream's page in their browser while you type:

ASCIINEMA_SERVER_URL=https://<public-ip>/ asciinema stream -r

The CLI prints the stream's URL. Live streams use WebSockets on port 443 through nginx; connections from other web sites are refused.

Step 10: Administration

Signed in as the administrator, open /admin for users, recordings, streams and the background job and runtime dashboards. Anyone else who opens it is sent to the sign in page:

The asciinema admin dashboard with user, recording and stream counts, recent signups and recent recordings

Step 11: Your own DNS name and certificate

Point a DNS record at the instance, then set the name asciinema is served as. This mints a new self signed certificate for the name, points nginx, the Origin check and the app's share links (URL_HOST) at it, restarts the app and keeps the name across reboots:

sudo asciinema-set-hostname <your-domain>

To use a certificate from your own certificate authority, copy it over the generated pair and reload nginx (running asciinema-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/asciinema/asciinema.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/asciinema/asciinema.key
sudo nginx -t && sudo systemctl reload nginx

Step 12: Security checks

PostgreSQL has no TCP listener and only local peer authentication; no role has a password:

sudo -u postgres psql -tAX -c "SELECT rolname, rolpassword IS NULL AS no_password FROM pg_authid WHERE rolname IN ('postgres','asciinema') ORDER BY 1"
sudo -u postgres psql -tAX -c 'SHOW listen_addresses' | sed 's/^$/(empty: no TCP listener)/'
sudo grep -vE '^\s*(#|$)' /etc/postgresql/16/main/pg_hba.conf
asciinema|t
postgres|t
(empty: no TCP listener)
local   all         postgres    peer
local   asciinema   asciinema   peer

Port 80 redirects to HTTPS, unknown host names are dropped, and the admin panel needs an administrator session:

H=$(sudo sed -n 's#^asciinema.url=https://\(.*\)/#\1#p' /root/asciinema-credentials.txt)
curl -s -o /dev/null -w 'http  -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'https -> %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' -H 'Host: other.example' https://127.0.0.1/ || true
curl -sk -o /dev/null -w 'admin (anonymous) -> %{http_code} %{redirect_url}\n' --resolve "$H:443:127.0.0.1" "https://$H/admin"
http  -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
https -> 200
unknown host -> 000
admin (anonymous) -> 302 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/login/new

The app container runs unprivileged and read only, and no service can reach the instance metadata service:

sudo docker inspect asciinema --format 'user={{.Config.User}} readonly={{.HostConfig.ReadonlyRootfs}} capdrop={{.HostConfig.CapDrop}}'
systemctl show nginx docker asciinema postgresql@16-main -p IPAddressDeny | sort -u | grep .
sudo iptables -S DOCKER-USER
user=2201:2201 readonly=true capdrop=[ALL]
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
-N DOCKER-USER
-A DOCKER-USER -d 168.63.129.16/32 -j DROP
-A DOCKER-USER -d 169.254.169.254/32 -j DROP

nginx passes the app a fixed host name and the real client address from the connection, never values a client sends, and forwards an Upgrade header only for WebSockets:

grep -E 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP|Upgrade)' /etc/nginx/asciinema-proxy.conf
proxy_set_header Host $app_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Upgrade $cloudimg_upgrade;

SSH allows key login only, for the ubuntu user:

sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|x11forwarding) '
permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
x11forwarding no

Step 13: Configuration, users and mail

The app reads /etc/asciinema/asciinema.env (root only; it holds the session key and Erlang cookie, so never share it). The image sets:

Variable Meaning Image default
URL_HOST / URL_SCHEME / URL_PORT Host of share links and the allowed browser origin your host / https / 443 (use Step 11 to change)
SIGN_UP_DISABLED Public sign up true (closed)
UPLOAD_AUTH_REQUIRED Uploads need a CLI linked to an account 1
ADMIN_PANEL_ON_MAIN_ENDPOINT /admin for administrators 1
UPLOAD_SIZE_LIMIT Largest upload in bytes 16000000
DATABASE_URL Local socket, peer authentication ecto://asciinema@localhost/asciinema?socket_dir=/var/run/postgresql

To let other people sign in, configure an SMTP relay so sign in links can be mailed: add SMTP_HOST, SMTP_PORT, SMTP_USERNAME and SMTP_PASSWORD to the file, then run sudo systemctl restart asciinema. While you invite people you can open sign up with SIGN_UP_DISABLED=false and a restart; close it again afterwards. Anyone can register while it is open.

Step 14: Backups and data

Recordings, the render cache and the database are on the data volume:

sudo -u postgres pg_lsclusters
df -h /var/lib/asciinema | tail -1 | awk '{print $1, $2, $6}'
Ver Cluster Port Status Owner    Data directory                    Log file
16  main    5432 online postgres /var/lib/asciinema/pgdata/16/main /var/log/postgresql/postgresql-16-main.log
/dev/nvme1n1 32G /var/lib/asciinema

(The cluster's configured port 5432 is not open: listen_addresses is empty.) Snapshot the data volume to back everything up, or dump the database with sudo -u postgres pg_dump asciinema > asciinema.sql and copy /var/lib/asciinema/app elsewhere.

Step 15: Server components and versions

head -2 /usr/share/doc/cloudimg/asciinema-build.txt
sudo docker ps --format '{{.Names}}  {{.Image}}  {{.Status}}  {{.Ports}}'
asciinema server 20260626 built by cloudimg from 38a908d8cf3312bbdfd18cf47d58eb42046a2655
patched libraries: plug 1.19.3, phoenix 1.7.24, cowlib 2.16.1, cowboy 2.15.0, html_sanitize_ex 1.4.5
asciinema  cloudimg/asciinema-server:20260626-cloudimg  Up 4 minutes  127.0.0.1:4000->4000/tcp
Component Path
App service asciinema.service (runs the container)
App configuration /etc/asciinema/asciinema.env (root only)
Recordings and cache /var/lib/asciinema/app
PostgreSQL cluster /var/lib/asciinema/pgdata/16/main
nginx site /etc/nginx/sites-available/cloudimg-asciinema
Certificate and key /etc/ssl/asciinema/
Sign in link helper /usr/local/sbin/asciinema-login-url
Host name helper /usr/local/sbin/asciinema-set-hostname
Build record and final mix.lock /usr/share/doc/cloudimg/asciinema-build.txt, asciinema-mix.lock
Credentials /root/asciinema-credentials.txt (mode 0600)

The app's own log (which includes request parameters such as sign in tokens) is kept in the container's root only log, sudo docker logs asciinema, and never reaches the system journal.

Known upstream issue in 20260626: deleting a recording or stream through the API (DELETE /api/v1/recordings/:id, DELETE /api/v1/streams/:id) deletes it but answers HTTP 500, because the server sends a body with its 204 response. The same happens with the unmodified upstream 20260626 release. Deleting from the web interface, and the CLI's record, upload and stream commands, are not affected.

Step 16: Security recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Keep /root/asciinema-credentials.txt and /etc/asciinema/asciinema.env secret.
  • Use your own DNS name and a certificate your viewers trust (Step 11).
  • Keep public sign up closed unless you are inviting people, and keep uploads limited to linked CLIs.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.
  • Snapshot the data volume regularly.

Support and licensing

asciinema server is open source under the Apache License 2.0; there is no per user or per CPU licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA