gov 06e085fb6d
Deploy PDS / deploy (push) Canceled after 0s
chore(cd): gitea autodeploy on push to main
2026-08-04 12:43:02 -04:00
2026-07-31 17:00:42 -04:00
2026-07-31 17:00:42 -04:00
2026-07-31 17:00:42 -04:00
2026-07-30 23:20:48 +00:00

atproto-pds-ansible

Ansible playbook for deploying a self-hosted Bluesky PDS with Caddy (TLS) and Watchtower (auto-updates).

What it deploys

  • PDS — Bluesky Personal Data Server (ghcr.io/bluesky-social/pds:0.4)
  • Caddy — Reverse proxy with automatic TLS via Let's Encrypt
  • Watchtower — Pulls new PDS image versions hourly and recreates containers

Requirements

  • A server running Ubuntu 20.04/22.04/24.04 or Debian 11/12/13
  • SSH access as the sysadmin user (with sudo privileges)
  • A domain pointed at the server (A record for the hostname + wildcard *. subdomain for handles)
  • Ports 80 and 443 open to the internet
  • Ansible installed locally

Setup

cp inventory.yml.example inventory.yml       # set your server IP
cp vars/main.yml.example vars/main.yml       # set hostname, admin email, S3/SMTP creds

Edit inventory.yml:

all:
  hosts:
    pds:
      ansible_host: 203.0.113.10

Edit vars/main.yml:

pds_hostname: bsky.example.com
pds_admin_email: you@example.com
pds_blobstore_s3_access_key: your-access-key
pds_blobstore_s3_secret_key: your-secret-key

Deploy

ansible-playbook playbook.yml

What it does

First run: the official PDS installer bootstraps Docker, generates secrets, and creates the initial database. Ansible then reads the generated secrets from pds.env, templates pds.env, compose.yaml, and Caddyfile, and runs docker compose up -d.

Subsequent runs: the installer is skipped (guarded by account.sqlite). Ansible reads the existing secrets from pds.env and re-templates all config files. Changes only trigger a restart if content actually changed.

Variables

Variable Required Default Description
pds_hostname yes — Public DNS name for the PDS
pds_admin_email yes — Admin contact email (used by Caddy for Let's Encrypt)
pds_data_dir no /pds Data directory (must be /pds — the installer enforces this)
pds_image_tag no 0.4 PDS Docker image tag
pds_blob_upload_limit no 104857600 Max blob upload size in bytes
pds_rate_limits_enabled no true Enable rate limiting
pds_invite_required no true Require invite codes to create accounts
pds_blobstore_type no disk Blob storage backend (disk or s3)
pds_email_from_address no — From address for outgoing emails
pds_blobstore_s3_access_key yes (if S3) — S3 access key for blob storage
pds_blobstore_s3_secret_key yes (if S3) — S3 secret key for blob storage
pds_email_smtp_url no — SMTP URL (with credentials) for email verification

Auto-updates

Watchtower checks every hour (WATCHTOWER_POLL_INTERVAL=3600) for new 0.4.x images on ghcr.io/bluesky-social/pds:0.4. When a new image is found, it pulls it and recreates the container. Old images are cleaned up automatically.

Post-deploy

Check that the PDS is healthy:

curl https://your-hostname/xrpc/_health

Create an account:

pdsadmin account create

Notes

  • pds.env is Ansible-managed. Secrets (JWT, admin password, PLC rotation key) are generated by the installer on first run, then read and preserved by Ansible on subsequent runs. To change config like S3 or SMTP, update vars/main.yml and re-run the playbook.
  • The installer creates a pds.service systemd unit. It's left in place — it just runs docker compose up -d on boot. Docker's restart: unless-stopped policy also handles this independently.

CI/CD (Gitea Actions)

Auto-deploys on push to main. Set up these in Gitea repo settings (Settings > Actions):

Variables (non-sensitive config):

Variable Description
SERVER_IP Target server IP address
PDS_HOSTNAME Public DNS name for the PDS (e.g., pds.example.com)
PDS_ADMIN_EMAIL Admin contact email

Secrets (sensitive credentials):

Secret Description
SSH_PRIVATE_KEY Private SSH key for the sysadmin user on the target server
OVH_S3_ACCESS_KEY OVH S3 access key for blob storage
OVH_S3_SECRET_KEY OVH S3 secret key for blob storage

The workflow installs Ansible, writes the SSH key from a secret, generates inventory.yml and vars/main.yml from vars and secrets, and runs the playbook.

S
Description
Config for atproto PDS services using ansible
Readme MIT
126 KiB
1 Stars 8 Watchers 0 Forks
Languages
Jinja 100%