Ryot
Overview
Ryot ("Roll your own tracker") is an open source, self-hosted tracker and aggregator for the things you consume. It records films, television, books, video games, audiobooks, podcasts, manga and anime alongside workouts, exercises and body measurements. It pulls artwork and metadata from public providers, so you search for a title instead of typing it out. Everything lands in one library with collections, watchlists, progress, ratings and reviews, plus importers that bring history across from other trackers and a GraphQL API for automation.
This SecureAnyCloud image runs Ryot v10.4.0 from the official container image, pinned to an immutable digest. PostgreSQL 16 is co-hosted on the same VM, with its data directory on a dedicated Azure data disk, and nginx fronts the stack on port 80. Every VM generates its own administrator account, administrator password, database password and admin access token on first boot, and public sign-up is switched off.
What you get:
- Ryot v10.4.0 (container
ghcr.io/ignisda/ryot, GPL-3.0), pinned to an immutablesha256digest so the shipped version never drifts - PostgreSQL 16, with its data directory on a dedicated 20 GiB Azure data disk mounted at
/var/lib/postgresql - nginx on
:80reverse proxying to the loopback-bound Ryot backend, with WebSocket upgrade headers and an unauthenticated/healthzendpoint for load balancer and uptime checks - A per-VM administrator account, administrator password and Ryot
SERVER_ADMIN_ACCESS_TOKEN, all generated on first boot and recorded in a root-only file - Public self-registration disabled by default, so no stranger can claim an account on your instance
- Upstream usage telemetry disabled by default (
DISABLE_TELEMETRY=true) docker.service,postgresql.service,nginx.service,ryot.serviceandryot-firstboot.serviceas enabled, active systemd units- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is the recommended size and is comfortable for a personal or small-team instance. Move up to Standard_B2ms or Standard_B4ms if you plan to import a very large history or share the instance with many people.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp from wherever you will use Ryot. Ryot serves 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 5432 so that the Ryot container can reach it across the Docker bridge. Its host-based authentication admits only the ryot role from the Docker subnet plus the local socket. Never open 5432 in an NSG rule.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal, choose Create a resource, search for Ryot on Ubuntu 24.04 LTS by SecureAnyCloud, and select Create.
Pick your subscription, resource group and region, and set the size to Standard_B2s. Choose SSH public key authentication with the username azureuser, and allow inbound 22 and 80.
The image already contains the dedicated data disk definition, so Azure creates and mounts the PostgreSQL volume for you.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:ryot-ubuntu-24-04:default:latest
az vm create \
--resource-group my-ryot-rg \
--name my-ryot \
--image sac1647283583153:ryot-ubuntu-24-04:default:latest \
--size Standard_B2s \
--storage-sku StandardSSD_LRS \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard \
--os-disk-delete-option Delete --nic-delete-option Delete
# then open the port Ryot serves on
az vm open-port --resource-group my-ryot-rg --name my-ryot --port 80 --priority 900Run both commands on your own workstation, not on the VM.
Step 3 - Connect to your VM
ssh azureuser@<vm-ip>First boot takes roughly 60 to 90 seconds. During that time, ryot-firstboot.service rotates the database password, mints the per-VM secrets, creates your administrator account, and only then starts nginx. Port 80 stays closed until it finishes.
Step 4 - Confirm the services are running
systemctl is-active docker postgresql nginx ryot ryot-firstbootAll five report active:
active active active active activeThe Ryot container and the dedicated PostgreSQL data disk:
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' df -h /var/lib/postgresql | tail -2NAMES IMAGE STATUS ryot 69abca475e2b Up 3 minutes Filesystem Size Used Avail Use% Mounted on /dev/sdc 20G 67M 19G 1% /var/lib/postgresqlStep 5 - Retrieve your per-VM administrator credentials
Every VM generates its own credentials on first boot and writes them to a root-only file. Nothing is shared between deployments, and there is no default password to change. Read the file:
sudo cat /root/ryot-credentials.txtThe file contains four values plus guidance:
ryot.url=http://<your-vm-address>/ ryot.admin.username=sacadmin ryot.admin.password=<24 characters, unique to this VM> ryot.admin.access.token=<64 characters, unique to this VM>To read a single value without printing the whole file:
sudo grep '^ryot.admin.username=' /root/ryot-credentials.txt | cut -d= -f2-ryot.admin.access.token is Ryot's SERVER_ADMIN_ACCESS_TOKEN. It authorizes server administrator GraphQL operations, including creating users while public sign-up is disabled. Treat it like a root password.
Step 6 - Confirm the health endpoints
nginx serves a static, unauthenticated /healthz for load balancer and uptime probes. It also proxies Ryot's own /health:
curl -s -o /dev/null -w 'nginx /healthz -> HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'ryot /health -> HTTP %{http_code}\n' http://127.0.0.1/healthnginx /healthz -> HTTP 200 ryot /health -> HTTP 200Confirm the version that Ryot reports:
curl -s -m 15 http://127.0.0.1/backend/graphql -H 'Content-Type: application/json' \
-d '{"query":"query { coreDetails { version } }"}'{"data":{"coreDetails":{"version":"v10.4.0"}}}The first boot log shows that nginx starts last, after the instance is locked down:
journalctl -u ryot-firstboot.service --no-pager | tail -6Step 7 - Sign in to the web interface
Browse to http://<your-vm-address>/. Ryot redirects to /auth. There is no sign-up link, because public registration is disabled on this image. The only way in is the per-VM administrator.
Sign in with ryot.admin.username and ryot.admin.password from Step 5. The dashboard opens and summarizes what is upcoming, in progress and recently finished.
Step 8 - Track your first title
Choose a media type in the left-hand navigation, for example Media then Anime, and switch to the Search tab. The search queries a public metadata provider and returns titles with cover art, release years and ratings.
Select a result to open its details page. It shows artwork, release year, episode or chapter counts, community rating, genres, a synopsis and the full cast, with tabs for Actions, History, Reviews and Suggestions.
To record progress, open the Actions tab, choose Update progress, then Mark ... as watched, and Submit the date form. The title now appears in your own library with a watch count, and on the dashboard under In Progress.
Step 9 - Verify authentication from the command line
Ryot exposes the same GraphQL API that the web interface uses, at /backend/graphql. Sign in with the per-VM administrator to obtain an API token, then read your collections back. The commands read the password straight from the credentials file, so you can copy and paste them as they are:
CREDS=/root/ryot-credentials.txt
U=$(sudo grep '^ryot.admin.username=' "$CREDS" | cut -d= -f2-)
P=$(sudo grep '^ryot.admin.password=' "$CREDS" | cut -d= -f2-) GQL=http://127.0.0.1/backend/graphql Q="mutation { loginUser(input: { password: { username: \"$U\", password: \"$P\" } }) { __typename ... on ApiKeyResponse { apiKey } ... on LoginError { error } } }"
TOKEN=$(curl -s -m 20 "$GQL" -H 'Content-Type: application/json' \
-d "$(jq -nc --arg q "$Q" '{query:$q}')" | jq -r '.data.loginUser.apiKey')
echo "API token issued: ${#TOKEN} characters" curl -s -m 20 "$GQL" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" \
-d '{"query":"query { userCollectionsList { response { name } } }"}' \
| jq -r '[.data.userCollectionsList.response[].name] | join(", ")'API token issued: 56 characters In Progress, Monitoring, Completed, Reminders, Watchlist, Custom, OwnedThese seven collections are the ones Ryot creates for a new account. An unauthenticated request to a protected query is refused:
curl -s -m 20 http://127.0.0.1/backend/graphql -H 'Content-Type: application/json' \
-d '{"query":"query { userCollectionsList { cacheId } }"}' | jq -r '.errors[0].message'NO_USER_IDStep 10 - Confirm the security posture
Upstream Ryot allows public sign-up by default and makes the first account to register the administrator. This image closes that gap. Registration is disabled before the application starts, and the administrator is created with a per-VM password. You can check each of these points on your own VM.
Anonymous self-registration is refused:
GQL=http://127.0.0.1/backend/graphql Q='mutation { registerUser(input: { data: { password: { username: "anon", password: "Anon123!" } } }) { __typename ... on RegisterError { error } } }' curl -s -m 20 "$GQL" -H 'Content-Type: application/json' \
-d "$(jq -nc --arg q "$Q" '{query:$q}')" | jq -r '.data.registerUser.error'DISABLEDNeither PostgreSQL account accepts a guessable password, and no swap is present:
for p in ryot postgres password; do PGPASSWORD=$p psql -w -h 127.0.0.1 -U ryot -d ryot -tAc 'SELECT 1' >/dev/null 2>&1 </dev/null \
&& echo "role ryot + '$p' -> ACCEPTED" || echo "role ryot + '$p' -> refused" done grep '^USERS_ALLOW_REGISTRATION=' /etc/ryot/ryot.env grep '^DISABLE_TELEMETRY=' /etc/ryot/ryot.envrole ryot + 'ryot' -> refused role ryot + 'postgres' -> refused role ryot + 'password' -> refused USERS_ALLOW_REGISTRATION=false DISABLE_TELEMETRY=trueStep 11 - The PostgreSQL datastore on its dedicated disk
Every user, tracked title, collection, review, workout and import lives in PostgreSQL. Its data directory is on the dedicated data disk rather than the OS disk, so you can resize or snapshot your Ryot data independently of the operating system.
sudo -u postgres psql -tAc 'SHOW data_directory'
sudo -u postgres psql -tAc 'SHOW server_version' | tr -d ' ' findmnt -no SOURCE,TARGET,FSTYPE /var/lib/postgresql/var/lib/postgresql/16/main 16.14(Ubuntu16.14-0ubuntu0.24.04.1) /dev/sdc /var/lib/postgresql ext4The mount is keyed by filesystem UUID in /etc/fstab with nofail, so it is restored on every boot. Confirm it:
grep postgresql /etc/fstabAdding more people to your instance
Public sign-up is disabled, so nobody can claim an account on your instance before you do. To add someone, sign in as the administrator and open Settings then Users. This creates the account for them.
To run an open instance instead, set USERS_ALLOW_REGISTRATION=true in /etc/ryot/ryot.env and restart Ryot. Do this only if you are happy for anyone who can reach the instance to register.
# This is the setting as shipped:
grep '^USERS_ALLOW_REGISTRATION=' /etc/ryot/ryot.env
# To open public sign up, flip it and restart Ryot:
#
sudo sed -i 's/^USERS_ALLOW_REGISTRATION=false$/USERS_ALLOW_REGISTRATION=true/' /etc/ryot/ryot.env
#
sudo systemctl restart ryotConnecting metadata providers
Ryot ships with no third-party API keys, so the providers that do not need one work immediately: Anilist and MangaUpdates for anime and manga, Audible for audiobooks, iTunes for podcasts, OpenLibrary for books, and the built-in exercise database for fitness.
Films, television, video games, music and some book sources need a free credential of your own. Add the relevant variables to /etc/ryot/ryot.env and restart Ryot. Each line below gives the media type, the variable and where to get its value:
Films and television: MOVIES_AND_SHOWS_TMDB_ACCESS_TOKEN. Create a free TMDB account and use its API read access token.
Films and television: MOVIES_AND_SHOWS_TVDB_API_KEY. Use a TheTVDB subscriber key.
Video games: VIDEO_GAMES_TWITCH_CLIENT_ID and VIDEO_GAMES_TWITCH_CLIENT_SECRET. Create a Twitch developer application, which is used for IGDB.
Music: MUSIC_SPOTIFY_CLIENT_ID and MUSIC_SPOTIFY_CLIENT_SECRET. Create a Spotify developer application.
Books: BOOKS_HARDCOVER_API_KEY. Use a Hardcover account.
echo 'MOVIES_AND_SHOWS_TMDB_ACCESS_TOKEN=<your-token>' | sudo tee -a /etc/ryot/ryot.env
sudo systemctl restart ryotServing Ryot on your own domain
FRONTEND_URL is used when Ryot generates links, so point it at the address that people actually use. Set it, then restart:
sudo sed -i 's|^FRONTEND_URL=.*|FRONTEND_URL=https://ryot.<your-domain>/|' /etc/ryot/ryot.env
sudo systemctl restart ryotTerminate TLS in front of the VM with an Azure Application Gateway or Front Door, or add your own certificate to the VM's nginx at /etc/nginx/sites-available/sac-ryot. The proxy already forwards X-Forwarded-Proto, so Ryot generates https links once TLS is in front of it.
Maintenance
- Configuration. The configuration lives in
/etc/ryot/ryot.env(mode0600, root only). Any change takes effect withsudo systemctl restart ryot. - Logs. Run
sudo docker logs --tail 100 ryotfor the application, andsudo journalctl -u ryot -u ryot-firstboot -u nginxfor the units. - Backups. Back up the
ryotPostgreSQL database, and take Azure snapshots of the data disk mounted at/var/lib/postgresql. - Upgrades. The container image is pinned to a digest, so your version never changes underneath you. To move to a newer Ryot, update the
image:line in/etc/ryot/compose.yamlto the new digest and restartryot.service. Ryot applies its own database migrations on start. Read the upstream release notes first. - OS updates. Unattended security upgrades are enabled, exactly as on stock Ubuntu 24.04.
Support
SecureAnyCloud publishes this image and provides 24/7 support for it. Raise a request at secureanycloud.com and include the offer name, the region, and the output of systemctl is-active docker postgresql nginx ryot ryot-firstboot.
Ryot itself is open source under GPL-3.0. Upstream project documentation is at docs.ryot.io.
Deploy on Azure
Launch Ryot on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
This image runs Ryot, a self-hosted media tracker and aggregator that keeps everything you read, watch, play and listen to in one place. Ryot tracks movies, TV shows, books, video games, audiobooks, podcasts, manga and anime, lets you organise them into collections, rate and review them, and see your progress and history across every kind of media on infrastructure you own.
Ryot ships as a single official Docker image (a Rust backend that serves the compiled web frontend on port 8000) that points at a PostgreSQL database. In this SecureAnyCloud image the Ryot container is bound to 127.0.0.1:8000 on the loopback interface and fronted by nginx as a reverse proxy on port 80 (and 443 once you add TLS). PostgreSQL is co-hosted on the same instance and reachable only from the loopback interface and the Docker bridge - the database port is never exposed by the security group. The Ryot container image is version-frozen to a pinned image digest so the bytes never change under you.
The entire Ryot datastore - users, tracked media, collections, reviews and import history - lives on a dedicated EBS data volume mounted at /var/lib/postgresql, independently resizable and separate from the operating system disk. On the first boot of every deployed instance, a one-shot service rotates the PostgreSQL password to a value unique to that instance, recreates an empty database, generates a per-instance admin access token, binds the application to the instance's own address, and records the token in /root/ryot-credentials.txt with mode 0600. No shared or default secrets ship in the image. Because Ryot makes the first account you register the administrator, the very first thing you do after launch is open the web interface and create your own admin account.
Prerequisites
Before you deploy this image you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and inbound ports 80 and 443 from the networks that will reach the application
- The AWS CLI (version 2) installed locally if you plan to deploy from the command line
- A DNS domain you control, if you intend to serve Ryot on your own hostname with TLS (covered in Step 7). You can run and verify the application on its own AWS address without a domain first.
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images choose AWS Marketplace AMIs and search for Ryot. Select the SecureAnyCloud listing and choose Select, then Continue on the subscription summary.
Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings select your VPC and subnet, and either create or select a security group that allows inbound port 22 from your management network and inbound ports 80 and 443 from the networks that will reach the application. Leave the root volume at the default size or larger; the image adds a separate 20 GiB data volume for the PostgreSQL datastore automatically.
Select Launch instance. First boot initialisation takes a minute or two after the instance state becomes Running and the status checks pass, while PostgreSQL is prepared and the Ryot container starts.
Launch the Instance from the AWS CLI
The following block launches an instance from the SecureAnyCloud Ryot Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens ports 22, 80, and 443 as described above.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ryot-01}]'The command prints a JSON document on success. Note the instance ID, then retrieve its public address once it is running with aws ec2 describe-instances --instance-ids <instance-id> --query "Reservations[].Instances[].PublicIpAddress" --output text.
Connect and Retrieve the Admin Access Token
Connect over SSH with the key pair you selected and the public IP address from step 2. The SSH login user depends on the operating system of the AMI variant you launched:
AMI variant SSH login user
Ryot 10.4 on Ubuntu 24.04 ubuntu
ssh -i /path/to/your-key.pem ubuntu@<public-ip>A per-instance admin access token is generated on first boot and written to a root-only file. Read it with:
sudo cat /root/ryot-credentials.txtThe file records ryot.url (initially the instance's own AWS address) and ryot.admin.access.token. The admin access token is Ryot's SERVER_ADMIN_ACCESS_TOKEN - a per-instance secret that authorizes server-administration operations against the GraphQL API. The database password and this token also live in /etc/ryot/ryot.env (mode 0600). Note that this token is not your login: Ryot accounts are created through the web interface, and the first account you register becomes the administrator (see Step 5).
Verify the Application Is Healthy
The Ryot container listens on the loopback interface and nginx fronts it on port 80. Confirm the health endpoint answers and that the supporting services are running.
curl -s -o /dev/null -w 'ryot /health -> HTTP %{http_code}\n' http://localhost/healthA healthy instance returns HTTP 200. nginx also serves an unauthenticated static endpoint for load balancer and uptime checks:
curl -s -o /dev/null -w 'nginx /healthz -> HTTP %{http_code}\n' http://localhost/healthzConfirm the Docker engine, PostgreSQL, nginx and the Ryot service are all active:
sudo systemctl is-active docker postgresql nginx ryotYou can see the version-frozen container and its loopback port binding with:
sudo docker ps --format '{{.Image}} {{.Status}} {{.Ports}}'The Ryot GraphQL API is served under /backend/graphql. Ryot returns HTTP 200 for GraphQL requests and reports authorization outcomes in the response body, so an unauthenticated request to a protected query returns a NO_USER_ID error rather than an HTTP error code:
curl -s http://localhost/backend/graphql -H 'Content-Type: application/json' \
-d '{"query":"query { userCollectionsList { cacheId } }"}'Register the First Account (Administrator)
Open http://<public-ip>/ in a browser. Ryot redirects to its authentication page. Choose Create a new account?, then pick a username and password and select Register. The first account registered on the instance is automatically made the administrator.
Sign in with the account you just created. From the dashboard you can search any media type - movies, TV shows, books, video games, audiobooks, podcasts, manga and anime - and add items to your library. Each media entry has a rich detail page with metadata, cast and crew, progress tracking, reviews and collection membership.
Ryot creates a set of default collections for every account - Watchlist, In Progress, Completed, Monitoring, Reminders, Owned and Custom - and you can create your own from the Collections page.
Closing open registration
Registration is open by default so you can create that first administrator account. Once your accounts exist, you can prevent new sign-ups by setting USERS_ALLOW_REGISTRATION=false in the environment file and restarting the service:
echo 'USERS_ALLOW_REGISTRATION=false' | sudo tee -a /etc/ryot/ryot.env
sudo systemctl restart ryotWhere Your Data Lives
The complete Ryot datastore is held in the co-hosted PostgreSQL database, whose data directory sits on a dedicated EBS volume mounted at /var/lib/postgresql, separate from the operating system disk so you can resize, snapshot and back it up independently.
df -h /var/lib/postgresqlThe Ryot application configuration (database connection, front-end URL and admin token) is in /etc/ryot/ryot.env, and the pinned container definition is in /etc/ryot/compose.yaml. The database is reachable only from the loopback interface and the Docker bridge; port 5432 is never opened to the network by the security group.
Serve Ryot on Your Own Domain with TLS
The image ships domain-free and binds Ryot to the instance's own address so it is usable immediately. To serve it on your own name, point a DNS record for your-domain at the instance's public address, then set FRONTEND_URL in /etc/ryot/ryot.env to your URL and restart the service. Terminate TLS either on an AWS Application Load Balancer in front of the instance, or by adding your own certificate to nginx on the instance. The Ryot container itself continues to serve plain HTTP on the loopback interface; nginx (or the load balancer) handles HTTPS.
To change the front-end URL to your-domain, edit /etc/ryot/ryot.env, set FRONTEND_URL=https://your-domain/, and run sudo systemctl restart ryot.
Backup and Maintenance
Because all state is in PostgreSQL on the dedicated /var/lib/postgresql volume, your backup strategy is a standard PostgreSQL backup plus, optionally, EBS snapshots of the data volume. Take a logical dump of the Ryot database with the postgres superuser (run on the instance):
sudo -u postgres pg_dump ryot > /var/tmp/ryot-backup.sqlSchedule regular EBS snapshots of the data volume through Amazon Data Lifecycle Manager for point-in-time recovery, and enable EBS encryption on the data volume for encryption at rest. To upgrade Ryot, update the pinned image digest in /etc/ryot/compose.yaml to a newer ghcr.io/ignisda/ryot release and run sudo systemctl restart ryot; the Rust backend runs any required database migrations automatically on start.
Support
This image is maintained by SecureAnyCloud. For deployment assistance, configuration help, TLS termination, database administration, upgrades or any other question about the image, contact SecureAnyCloud support at sales@secureanycloud.com. Ryot is free and open source software distributed under the GNU General Public License v3.0; the complete corresponding source code is available at https://github.com/IgnisDa/ryot.
Deploy on AWS
Launch Ryot on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com