asciinema server 20260626 on AWS User Guide
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:4000behind 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:

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

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:

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

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:

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.txtand/etc/asciinema/asciinema.envsecret. - 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 upgradeapplies 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