MAIL_PROVIDER moved to web-app-stalwart without a make docs run, so every generated README still drew Mailu as the email dependency. This is the generator output for the current tree: the root role table and each role cosmos now name Stalwart, Mailu loses its dependents, and Stalwart gains its swarm marker, the Tor dependencies and the 0..0 edges. The hand-written cosmos of svc-net-tor-smtp gives way to the generated one, which no longer shows the SOCKS5 hop through svc-net-tor because the role does not declare that dependency. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
8.3 KiB
Bluesky
Description
Bluesky is a decentralized social network that runs on the open AT Protocol. Each user account lives on a Personal Data Server (PDS) that holds the user's posts, replies, and follows as signed records, and any number of independent app views can read those records to render a timeline.
Overview
This role deploys a self-hosted Bluesky stack on Docker Compose. It runs the official PDS container as the user-data layer, builds the upstream @bluesky-social/social-app web client as the user-facing UI, and adds an in-role login-broker sidecar that gates the web client through Keycloak via web-app-keycloak's SSO-proxy sidecar. The broker auto-provisions PDS accounts for incoming OIDC users, holds the synthesised app-password AES-256-GCM-encrypted in a process-local cache, and drops a usable PDS session into the social-app's local storage so end users never see the synthesised credential.
For administration commands see Administration.md. For DNS setup see Installation.md. For the broker's environment contract see README.md.
Cosmos
The diagram places Bluesky in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
flowchart LR
subgraph deps [Dependencies]
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
dep_svc_net_tor["svc-net-tor 🐳🐝"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_matomo["web-app-matomo 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
dep_web_svc_css["web-svc-css 💻"]
dep_web_svc_logout["web-svc-logout 🐳🐝"]
end
subgraph role [web-app-bluesky 🐳🐝]
svc_logout["logout"]
svc_dashboard["dashboard"]
svc_matomo["matomo"]
svc_email["email"]
svc_ldap["ldap ❌"]
svc_sso["sso"]
svc_web["web"]
svc_view["view"]
svc_pds["pds"]
svc_broker["broker"]
svc_sso_proxy["sso-proxy"]
svc_css["css"]
svc_prometheus["prometheus"]
svc_api["api"]
svc_bluesky["bluesky"]
svc_tor["tor"]
svc_container_backup["container_backup"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_svc_db_openldap -- "0..0" --> svc_ldap
dep_svc_net_tor -. "0..1" .-> svc_tor
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_keycloak -. "0..1" .-> svc_sso
dep_web_app_matomo -. "0..1" .-> svc_matomo
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_app_stalwart -. "0..1" .-> svc_email
dep_web_svc_css -. "0..1" .-> svc_css
dep_web_svc_logout -. "0..1" .-> svc_logout
linkStyle 1 stroke:red;
Solid 1:1 edges are fixed relationships; dashed 0..1 edges are conditional (enabled only in matching deployments); red 0..0 edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in meta/main.yml.
Features
- Decentralized social networking: Self-host the AT Protocol PDS and serve user data under your own domain.
- Containerized deployment: Run PDS, social-app, and the login-broker through Docker Compose with the project's standard role-meta wiring.
- Native Keycloak SSO: Sit a
web-app-keycloak's SSO-proxy sidecar sidecar in front of the broker so unauthenticated visitors are redirected to Keycloak before they ever reach the web client. - PDS auto-provisioning: Have the broker call
com.atproto.server.createAccounton first login and cache the resulting handle and DID per Keycloak username. - Encrypted app-password cache: Keep the synthesised PDS app-password in a process-local map only as AES-256-GCM ciphertext, with the key sourced from the project's credentials vault.
- Pinned upstream client: Build the
social-appweb client from a pinned tag so the broker'sBSKY_STORAGEhandoff stays in sync with the upstream persistence schema.
Quick Setup
Development
Clone, set up the workstation, and deploy Bluesky onto the local stack:
git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-app-bluesky full_cycle=false
Production
Run the published image to provision the inventory and deploy Bluesky to a managed server (the mounted volume persists the inventory):
APP=web-app-bluesky
HOST=<your-server>
DOMAIN=<your-domain>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"
docker run --rm -it \
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
-e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
ghcr.io/infinito-nexus/core/debian bash -c '
INVENTORY=/etc/infinito.nexus/inventories/production
infinito administration inventory provision "$INVENTORY" \
--inventory-file "$INVENTORY/devices.yml" \
--host "$HOST" \
--include "$APP" \
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
--password-file "$INVENTORY/.password" \
--diff -vv'
Single Sign-On
OIDC integration follows the variant A+ pattern documented in requirement 013: web-app-keycloak's SSO-proxy sidecar is the gate, the in-role login-broker is the session-handoff agent, and the official social-app web client is the user-facing UI.
The broker MUST receive X-Forwarded-User, X-Forwarded-Preferred-Username, and X-Forwarded-Email from oauth2-proxy. On a user's first authenticated visit the broker calls the PDS com.atproto.server.createAccount endpoint with a synthesised app-password, encrypts that password with AES-256-GCM under a key from the project's credentials vault, and caches the ciphertext in the broker's in-process sessionCache keyed by Keycloak username. Subsequent visits decrypt the cached password in-broker, exchange it for a PDS session via com.atproto.server.createSession, and seed localStorage["BSKY_STORAGE"] so the social-app finds an authenticated session at boot. A broker restart drops the cache; the next SSO visit triggers a fresh createAccount and the previous app-password remains valid in PDS until rotated.
LDAP rides the same broker path: Keycloak federates LDAP via svc-db-openldap and the broker reads the resulting Keycloak headers via oauth2-proxy. Direct LDAP-to-PDS sync is NOT supported, the Keycloak federation layer is the single authority for user identity.
RBAC stays gating-only because the PDS has no in-app role concept beyond "account exists / does not exist". Membership in the /roles/web-app-bluesky Keycloak group lets a user past the oauth2-proxy gate. The documented SSO/RBAC exception per lifecycle.md covers the in-app authorisation tier.
Developer Notes
See Administration.md for live container administration through pdsadmin. See README.md for the login-broker's environment contract and failure modes.
Further Resources
- Bluesky Official Website
- AT Protocol Documentation
- Bluesky PDS GitHub Repository
- Bluesky Social App GitHub Repository
- Self-hosting Bluesky with Docker and SWAG
- pdsadmin GitHub Repository
Credits
Implemented by Kevin Veen-Birkenbach. Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).