Deployment guide

asciinema server 20260626

Overview

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, with text you can select and copy rather than video. Recordings are small, searchable down to their content, and can be embedded, shared by secret link or made public, and the server also relays live terminal streams.

This SecureAnyCloud AMI runs asciinema server 20260626 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud developer and API tools listing on AWS Marketplace. You choose it as the software version when you subscribe and launch.

What you get:

  • 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 and html_sanitize_ex 1.4.5. The upstream 20260626 container pins older versions of those libraries, which 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. In this image, sign-up is closed before anything listens. First boot creates the one administrator, sacadmin, plus a command-line upload token, and 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 and listens on no TCP port. 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.
  • A fully patched base. Ubuntu 24.04 LTS is patched at build time, with unattended security upgrades switched on.
  • The full stack on the image. The SecureAnyCloud asciinema server 20260626 container image (sac/asciinema-server:20260626-sac), Docker Engine, PostgreSQL 16, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
  • A separate data volume. 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.
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, select your Region, and choose Launch through EC2.

Choose m5.large, your key pair and a security group as described in Prerequisites. Keep the data volume in the launch template, because it holds your recordings and database.

To launch with the AWS CLI instead, 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}]'
2

Connect to your instance

Version SSH user

asciinema server 20260626 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

On first boot, the instance creates the database, generates the secrets and the certificate, and creates the administrator. Then the app and nginx start. Confirm that they are running:

systemctl is-active asciinema-firstboot asciinema nginx postgresql@16-main docker
sudo test -f /var/lib/sac/data/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. Check the listeners:

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"))
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), so keep it secret:

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

The URL uses the instance's public DNS name from EC2 instance metadata. Requests to the instance's IP addresses redirect to that name. To use your own DNS name, see Step 11.

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 generate 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. Alternatively, install your own certificate (Step 11).

A link is valid for 60 minutes. Run the helper again to get a new one. Then open your account settings and replace admin@sac.invalid with your real e-mail address.

The home page lists recent public recordings:

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

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, using 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 the CLI 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.

7

Upload from the command line

The API is the same one the CLI uses. The following commands upload a short recording from the instance itself and print the share link that the API 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 sac\\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.

8

Play, share and search

Open a share link to replay the recording in the player. You can pause, seek and copy text straight out of the terminal. Select Share for the link and embed code, or Download for the .cast file:

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

9

Stream a terminal live

With the CLI linked to your account (Step 6), start a live stream. Viewers watch it on 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 websites are refused.

10

Administration

Sign in as the administrator and open /admin to manage users, recordings and streams, and to view the background job and runtime dashboards. Anyone else who opens this page is sent to the sign-in page:

11

Your own DNS name and certificate

Point a DNS record at the instance, then set the name that asciinema is served as. This generates a new self-signed certificate for the name and points nginx, the Origin check and the app's share links (URL_HOST) at it. It also 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 certificate:

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
12

Security checks

PostgreSQL has no TCP listener and uses 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 that a client sends. It 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 $sac_upgrade;

SSH allows key-based login only, for the ubuntu user:

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

Configuration, users and mail

The app reads /etc/asciinema/asciinema.env. This file is readable by root only and holds the session key and Erlang cookie, so never share it. The image sets the following variables:

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 that 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 by setting SIGN_UP_DISABLED=false and restarting the service. Close it again afterwards, because anyone can register while it is open.

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, because listen_addresses is empty. To back up everything, snapshot the data volume. Alternatively, dump the database with sudo -u postgres pg_dump asciinema > asciinema.sql and copy /var/lib/asciinema/app to another location.

15

Server components and versions

head -2 /usr/share/doc/sac/asciinema-build.txt
sudo docker ps --format '{{.Names}} {{.Image}} {{.Status}} {{.Ports}}'
asciinema server 20260626 built by sac 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 sac/asciinema-server:20260626-sac 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/sac-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/sac/asciinema-build.txt, asciinema-mix.lock

Credentials /root/asciinema-credentials.txt (mode 0600)

The app's own log includes request parameters such as sign-in tokens. It is kept in the container's root-only log, which you read with sudo docker logs asciinema, and it 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 unmodified upstream 20260626 release behaves the same way. Deleting from the web interface, and the CLI's record, upload and stream commands, are not affected.

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 that your viewers trust (Step 11).
  • Keep public sign-up closed unless you are inviting people, and limit uploads 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, and there is no per-user or per-CPU licence fee. The AMI price covers SecureAnyCloud's build, hardening, first-boot setup and support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24-hour response SLA

Deploy on AWS

Launch asciinema server 20260626 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS