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: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. - 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.
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}]'Connect to your instance
Version SSH user
asciinema server 20260626 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>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_DONEactive active active active active FIRSTBOOT_DONEOnly 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' | sort0.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"))Find your URL and administrator
sudo grep -E '^asciinema\.(url|admin)' /root/asciinema-credentials.txtThe 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.invalidThe 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.
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:
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.castasciinema 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.
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.
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:
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 -rThe CLI prints the stream's URL. Live streams use WebSockets on port 443 through nginx; connections from other websites are refused.
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:
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 nginxSecurity 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.confasciinema|t postgres|t (empty: no TCP listener) local all postgres peer local asciinema asciinema peerPort 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/newThe 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-USERuser=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 DROPnginx 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.confproxy_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 noConfiguration, 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.
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/asciinemaThe 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.
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/tcpComponent 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.
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 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 upgradeapplies 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