A fork of the Cocoon PDS but being made more distributed.
0

Configure Feed

Select the types of activity you want to include in your feed.

added some documentation and example config files

Signed-off-by: Will <did:plc:dadhhalkfcq3gucaq25hjqon>

author willdot.net committer
Tangled
date (Jun 18, 2026, 5:34 PM UTC) commit b2270116 parent 413b7049 change-id nyspztup
+43 -73
+22
.env copy.dist
··· 1 + COCOON_DID="did:web:cocoon.example.com" 2 + COCOON_HOSTNAME="cocoon.example.com" 3 + COCOON_ROTATION_KEY_PATH="./rotation.key" 4 + COCOON_JWK_PATH="./jwk.key" 5 + COCOON_CONTACT_EMAIL="me@example.com" 6 + COCOON_RELAYS=https://bsky.network 7 + # Generate with `openssl rand -hex 16` 8 + COCOON_ADMIN_PASSWORD= 9 + # Generate with `openssl rand -hex 32` 10 + COCOON_SESSION_SECRET= 11 + # Optional: Change the session cookie name (default: session) 12 + # COCOON_SESSION_COOKIE_KEY=cocoon_session 13 + # Set this to the Turso database URL that they provide you. It'll be something like libsql://your-database-here.aws-eu-west-1.turso.io 14 + COCOON_DATABASE_URL= 15 + # Set this to the Turso token that you generate 16 + COCOON_TURSO_TOKEN= 17 + COCOON_DB_TYPE="turso" 18 + # Set this to the public URL for your subscribe repos service 19 + SUBSCRIBE_REPOS_SERVICE_URL="http://subscribe-repos:4455/events" 20 + PUSH_BASED_EVENTS=true 21 + # Generate with `openssl rand -hex 32` This needs to then be used across all instances 22 + COCOON_NONCE_SECRET=
+16
README.md
··· 4 4 5 5 This is a fork of the Cocoon PDS with the intention of making it a distributed PDS. You can read my thoughts on why I'm trying this POC out [here](https://willdot.leaflet.pub/3miiirzf7jc2j) and follow along the series for updates. 6 6 7 + 8 + To run a distributed version of this PDS you'll need to use `docker-compose.dist.yaml` as that has the relevant examples of extra config required. It is slightly different in that it only contains the main app. The other helper services (create keys and invite codes can be run manually). 9 + 10 + 11 + There are also some extra requirements for setting this up: 12 + 13 + 1: You need to generate a nonce secret and set it as an environment variable `COCOON_NONCE_SECRET`. (Recommend running something like `openssl rand -hex 16`) 14 + 2: You need to have a Turso cloud database. See more information [here](https://turso.tech) 15 + 3: You need to generate a rotation and JWK keys using the `init-keys.sh` script. Both these keys need to be present on all of your nodes running a distributed Cocoon instance. 16 + 4: You need to have an app called `subscribe-repos` running somewhere. This acts as a kinda reverse subscribe repos. Read more about it [here](https://willdot.leaflet.pub/3miu5jbrsrc2p). To run it you can use the docker-compose in it's [repo](https://tangled.org/willdot.net/subscribe-repos) 17 + 5: Your reverse proxy needs to have some sort of load balancing so that it can route traffic between nodes. It also needs to reverse proxy the route `/xrpc/com.atproto.sync.subscribeRepos` to the `subscribe-repos` service, where ever you have that running. 18 + 19 + Once you have those requirements done you should be able to spin up a couple of instances of the service using the `.env.example.dist` as a guide on the environment variables to set. 20 + 21 + 22 + 7 23 > [!WARNING] 8 24 > I migrated and have been running my main account on this PDS for months now without issue, however, I am still not responsible if things go awry, particularly during account migration. Please use caution. 9 25
+3 -69
docker-compose.dist.yaml
··· 1 1 version: "3.8" 2 2 3 3 services: 4 - init-keys: 5 - build: 6 - context: . 7 - dockerfile: Dockerfile 8 - image: ghcr.io/haileyok/cocoon:latest 9 - container_name: cocoon-init-keys 10 - volumes: 11 - - ./keys:/keys 12 - - ./data:/data/cocoon 13 - - ./init-keys.sh:/init-keys.sh:ro 14 - environment: 15 - COCOON_DID: ${COCOON_DID} 16 - COCOON_HOSTNAME: ${COCOON_HOSTNAME} 17 - COCOON_ROTATION_KEY_PATH: /keys/rotation.key 18 - COCOON_JWK_PATH: /keys/jwk.key 19 - COCOON_CONTACT_EMAIL: ${COCOON_CONTACT_EMAIL} 20 - COCOON_RELAYS: ${COCOON_RELAYS:-https://bsky.network} 21 - COCOON_ADMIN_PASSWORD: ${COCOON_ADMIN_PASSWORD} 22 - entrypoint: ["/bin/sh", "/init-keys.sh"] 23 - restart: "no" 24 - 25 4 cocoon: 26 5 build: 27 6 context: . ··· 46 25 COCOON_RELAYS: ${COCOON_RELAYS:-https://bsky.network} 47 26 COCOON_ADMIN_PASSWORD: ${COCOON_ADMIN_PASSWORD} 48 27 COCOON_SESSION_SECRET: ${COCOON_SESSION_SECRET} 28 + COCOON_NONCE_SECRET: ${COCOON_NONCE_SECRET} 49 29 50 30 # Server configuration 51 31 COCOON_ADDR: ":8080" 52 - COCOON_DB_TYPE: ${COCOON_DB_TYPE:-sqlite} 53 - COCOON_DB_NAME: ${COCOON_DB_NAME:-/data/cocoon/cocoon.db} 32 + COCOON_DB_TYPE: ${COCOON_DB_TYPE:-turso} 54 33 COCOON_DATABASE_URL: ${COCOON_DATABASE_URL:-} 55 34 COCOON_BLOCKSTORE_VARIANT: ${COCOON_BLOCKSTORE_VARIANT:-sqlite} 56 - # Set this if using Turso 57 35 COCOON_TURSO_TOKEN: ${COCOON_TURSO_TOKEN:-} 58 36 59 37 # Optional: SMTP settings for email ··· 75 53 COCOON_S3_CDN_URL: ${COCOON_S3_CDN_URL:-} 76 54 77 55 # Configure pointing to the subscribe repos service as a sync repos service 78 - PUSH_BASED_EVENTS: ${PUSH_BASED_EVENTS:-false} 56 + PUSH_BASED_EVENTS: ${PUSH_BASED_EVENTS:-true} 79 57 SUBSCRIBE_REPOS_SERVICE_URL: ${SUBSCRIBE_REPOS_SERVICE_URL:-} 80 58 81 59 # Optional: Fallback proxy ··· 88 66 retries: 3 89 67 start_period: 40s 90 68 91 - create-invite: 92 - build: 93 - context: . 94 - dockerfile: Dockerfile 95 - image: ghcr.io/haileyok/cocoon:latest 96 - container_name: cocoon-create-invite 97 - network_mode: host 98 - volumes: 99 - - ./keys:/keys 100 - - ./data:/data/cocoon 101 - - ./create-initial-invite.sh:/create-initial-invite.sh:ro 102 - environment: 103 - COCOON_DID: ${COCOON_DID} 104 - COCOON_HOSTNAME: ${COCOON_HOSTNAME} 105 - COCOON_ROTATION_KEY_PATH: /keys/rotation.key 106 - COCOON_JWK_PATH: /keys/jwk.key 107 - COCOON_CONTACT_EMAIL: ${COCOON_CONTACT_EMAIL} 108 - COCOON_RELAYS: ${COCOON_RELAYS:-https://bsky.network} 109 - COCOON_ADMIN_PASSWORD: ${COCOON_ADMIN_PASSWORD} 110 - COCOON_DB_TYPE: ${COCOON_DB_TYPE:-sqlite} 111 - COCOON_DB_NAME: ${COCOON_DB_NAME:-/data/cocoon/cocoon.db} 112 - COCOON_DATABASE_URL: ${COCOON_DATABASE_URL:-} 113 - depends_on: 114 - - init-keys 115 - entrypoint: ["/bin/sh", "/create-initial-invite.sh"] 116 - restart: "no" 117 - 118 - caddy: 119 - image: caddy:2-alpine 120 - container_name: cocoon-caddy 121 - network_mode: host 122 - volumes: 123 - - ./Caddyfile:/etc/caddy/Caddyfile:ro 124 - - caddy_data:/data 125 - - caddy_config:/config 126 - restart: unless-stopped 127 - environment: 128 - COCOON_HOSTNAME: ${COCOON_HOSTNAME} 129 - CADDY_ACME_EMAIL: ${COCOON_CONTACT_EMAIL:-} 130 - 131 69 volumes: 132 70 data: 133 71 driver: local 134 - caddy_data: 135 - driver: local 136 - caddy_config: 137 - driver: local
+2 -4
server/server.go
··· 86 86 lastRequestCrawl time.Time 87 87 requestCrawlMu sync.Mutex 88 88 89 - dbName string 90 89 dbType string 91 90 s3Config *S3Config 92 91 } ··· 263 262 return nil, fmt.Errorf("addr must be set") 264 263 } 265 264 266 - if args.DbName == "" { 267 - return nil, fmt.Errorf("db name must be set") 265 + if args.DbType == "sqlite" && args.DbName == "" { 266 + return nil, fmt.Errorf("db name must be set for a sqlite app") 268 267 } 269 268 270 269 if args.Did == "" { ··· 469 468 evtman: events.NewEventManager(evtPersister), 470 469 passport: identity.NewPassport(h, identity.NewMemCache(10_000)), 471 470 472 - dbName: args.DbName, 473 471 dbType: dbType, 474 472 s3Config: args.S3Config, 475 473