forked from
willdot.net/cocoon
A fork of the Cocoon PDS but being made more distributed.
1# Cocoon
2
3> [!WARNING]
4> 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.
5
6Cocoon is a PDS implementation in Go. It is highly experimental, and is not ready for any production use.
7
8## Quick Start with Docker Compose
9
10### Prerequisites
11
12- Docker and Docker Compose installed
13- A domain name pointing to your server (for automatic HTTPS)
14- Ports 80 and 443 open in i.e. UFW
15
16### Installation
17
181. **Clone the repository**
19 ```bash
20 git clone https://github.com/haileyok/cocoon.git
21 cd cocoon
22 ```
23
242. **Create your configuration file**
25 ```bash
26 cp .env.example .env
27 ```
28
293. **Edit `.env` with your settings**
30
31 Required settings:
32 ```bash
33 COCOON_DID="did:web:your-domain.com"
34 COCOON_HOSTNAME="your-domain.com"
35 COCOON_CONTACT_EMAIL="you@example.com"
36 COCOON_RELAYS="https://bsky.network"
37
38 # Generate with: openssl rand -hex 16
39 COCOON_ADMIN_PASSWORD="your-secure-password"
40
41 # Generate with: openssl rand -hex 32
42 COCOON_SESSION_SECRET="your-session-secret"
43 ```
44
454. **Start the services**
46 ```bash
47 # Pull pre-built image from GitHub Container Registry
48 docker-compose pull
49 docker-compose up -d
50 ```
51
52 Or build locally:
53 ```bash
54 docker-compose build
55 docker-compose up -d
56 ```
57
58 **For PostgreSQL deployment:**
59 ```bash
60 # Add POSTGRES_PASSWORD to your .env file first!
61 docker-compose -f docker-compose.postgres.yaml up -d
62 ```
63
645. **Get your invite code**
65
66 On first run, an invite code is automatically created. View it with:
67 ```bash
68 docker-compose logs create-invite
69 ```
70
71 Or check the saved file:
72 ```bash
73 cat keys/initial-invite-code.txt
74 ```
75
76 **IMPORTANT**: Save this invite code! You'll need it to create your first account.
77
786. **Monitor the services**
79 ```bash
80 docker-compose logs -f
81 ```
82
83### What Gets Set Up
84
85The Docker Compose setup includes:
86
87- **init-keys**: Automatically generates cryptographic keys (rotation key and JWK) on first run
88- **cocoon**: The main PDS service running on port 8080
89- **create-invite**: Automatically creates an initial invite code after Cocoon starts (first run only)
90- **caddy**: Reverse proxy with automatic HTTPS via Let's Encrypt
91
92### Data Persistence
93
94The following directories will be created automatically:
95
96- `./keys/` - Cryptographic keys (generated automatically)
97 - `rotation.key` - PDS rotation key
98 - `jwk.key` - JWK private key
99 - `initial-invite-code.txt` - Your first invite code (first run only)
100- `./data/` - SQLite database and blockstore
101- Docker volumes for Caddy configuration and certificates
102
103### Optional Configuration
104
105#### Database Configuration
106
107By default, Cocoon uses SQLite which requires no additional setup. For production deployments with higher traffic, you can use PostgreSQL:
108
109```bash
110# Database type: sqlite (default) or postgres
111COCOON_DB_TYPE="postgres"
112
113# PostgreSQL connection string (required if db-type is postgres)
114# Format: postgres://user:password@host:port/database?sslmode=disable
115COCOON_DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable"
116
117# Or use the standard DATABASE_URL environment variable
118DATABASE_URL="postgres://cocoon:password@localhost:5432/cocoon?sslmode=disable"
119```
120
121For SQLite (default):
122```bash
123COCOON_DB_TYPE="sqlite"
124COCOON_DB_NAME="/data/cocoon/cocoon.db"
125```
126
127> **Note**: When using PostgreSQL, database backups to S3 are not handled by Cocoon. Use `pg_dump` or your database provider's backup solution instead.
128
129#### SMTP Email Settings
130```bash
131COCOON_SMTP_USER="your-smtp-username"
132COCOON_SMTP_PASS="your-smtp-password"
133COCOON_SMTP_HOST="smtp.example.com"
134COCOON_SMTP_PORT="587"
135COCOON_SMTP_EMAIL="noreply@example.com"
136COCOON_SMTP_NAME="Cocoon PDS"
137```
138
139#### S3 Storage
140
141Cocoon supports S3-compatible storage for both database backups (SQLite only) and blob storage (images, videos, etc.):
142
143```bash
144# Enable S3 backups (SQLite databases only - hourly backups)
145COCOON_S3_BACKUPS_ENABLED=true
146
147# Enable S3 for blob storage (images, videos, etc.)
148# When enabled, blobs are stored in S3 instead of the database
149COCOON_S3_BLOBSTORE_ENABLED=true
150
151# S3 configuration (works with AWS S3, MinIO, Cloudflare R2, etc.)
152COCOON_S3_REGION="us-east-1"
153COCOON_S3_BUCKET="your-bucket"
154COCOON_S3_ENDPOINT="https://s3.amazonaws.com"
155COCOON_S3_ACCESS_KEY="your-access-key"
156COCOON_S3_SECRET_KEY="your-secret-key"
157
158# Optional: CDN/public URL for blob redirects
159# When set, com.atproto.sync.getBlob redirects to this URL instead of proxying
160COCOON_S3_CDN_URL="https://cdn.example.com"
161```
162
163**Blob Storage Options:**
164- `COCOON_S3_BLOBSTORE_ENABLED=false` (default): Blobs stored in the database
165- `COCOON_S3_BLOBSTORE_ENABLED=true`: Blobs stored in S3 bucket under `blobs/{did}/{cid}`
166
167**Blob Serving Options:**
168- Without `COCOON_S3_CDN_URL`: Blobs are proxied through the PDS server
169- With `COCOON_S3_CDN_URL`: `getBlob` returns a 302 redirect to `{CDN_URL}/blobs/{did}/{cid}`
170
171> **Tip**: For Cloudflare R2, you can use the public bucket URL as the CDN URL. For AWS S3, you can use CloudFront or the S3 bucket URL directly if public access is enabled.
172
173#### Alpine based image
174
175The default image is based on Debian. You can use the Alpine-based image if you prefer.
176
177> [!NOTE]
178> Currently, we do not have pre-built Alpine-based image on the GitHub Container Registry. You have to build them locally.
179
180In the compose file, replace every `dockerfile: Dockerfile` by `dockerfile: Dockerfile.alpine`, e.g.
181```yml
182services:
183 cocoon:
184 build:
185 context: .
186 dockerfile: Dockerfile.alpine
187```
188
189You can also build the image locally with
190```bash
191docker build -f Dockerfile.alpine -t cocoon:alpine .
192```
193
194### Management Commands
195
196Create an invite code:
197```bash
198docker exec cocoon-pds /cocoon create-invite-code --uses 1
199```
200
201Reset a user's password:
202```bash
203docker exec cocoon-pds /cocoon reset-password --did "did:plc:xxx"
204```
205
206### Updating
207
208```bash
209docker-compose pull
210docker-compose up -d
211```
212
213## Implemented Endpoints
214
215> [!NOTE]
216Just because something is implemented doesn't mean it is finished. Tons of these are returning bad errors, don't do validation properly, etc. I'll make a "second pass" checklist at some point to do all of that.
217
218### Identity
219
220- [x] `com.atproto.identity.getRecommendedDidCredentials`
221- [x] `com.atproto.identity.requestPlcOperationSignature`
222- [x] `com.atproto.identity.resolveHandle`
223- [x] `com.atproto.identity.signPlcOperation`
224- [x] `com.atproto.identity.submitPlcOperation`
225- [x] `com.atproto.identity.updateHandle`
226
227### Repo
228
229- [x] `com.atproto.repo.applyWrites`
230- [x] `com.atproto.repo.createRecord`
231- [x] `com.atproto.repo.putRecord`
232- [x] `com.atproto.repo.deleteRecord`
233- [x] `com.atproto.repo.describeRepo`
234- [x] `com.atproto.repo.getRecord`
235- [x] `com.atproto.repo.importRepo` (Works "okay". Use with extreme caution.)
236- [x] `com.atproto.repo.listRecords`
237- [x] `com.atproto.repo.listMissingBlobs`
238
239### Server
240
241- [x] `com.atproto.server.activateAccount`
242- [x] `com.atproto.server.checkAccountStatus`
243- [x] `com.atproto.server.confirmEmail`
244- [x] `com.atproto.server.createAccount`
245- [x] `com.atproto.server.createInviteCode`
246- [x] `com.atproto.server.createInviteCodes`
247- [x] `com.atproto.server.deactivateAccount`
248- [x] `com.atproto.server.deleteAccount`
249- [x] `com.atproto.server.deleteSession`
250- [x] `com.atproto.server.describeServer`
251- [ ] `com.atproto.server.getAccountInviteCodes`
252- [x] `com.atproto.server.getServiceAuth`
253- ~~[ ] `com.atproto.server.listAppPasswords`~~ - not going to add app passwords
254- [x] `com.atproto.server.refreshSession`
255- [x] `com.atproto.server.requestAccountDelete`
256- [x] `com.atproto.server.requestEmailConfirmation`
257- [x] `com.atproto.server.requestEmailUpdate`
258- [x] `com.atproto.server.requestPasswordReset`
259- [x] `com.atproto.server.reserveSigningKey`
260- [x] `com.atproto.server.resetPassword`
261- ~~[] `com.atproto.server.revokeAppPassword`~~ - not going to add app passwords
262- [x] `com.atproto.server.updateEmail`
263
264### Sync
265
266- [x] `com.atproto.sync.getBlob`
267- [x] `com.atproto.sync.getBlocks`
268- [x] `com.atproto.sync.getLatestCommit`
269- [x] `com.atproto.sync.getRecord`
270- [x] `com.atproto.sync.getRepoStatus`
271- [x] `com.atproto.sync.getRepo`
272- [x] `com.atproto.sync.listBlobs`
273- [x] `com.atproto.sync.listRepos`
274- ~~[ ] `com.atproto.sync.notifyOfUpdate`~~ - BGS doesn't even have this implemented lol
275- [x] `com.atproto.sync.requestCrawl`
276- [x] `com.atproto.sync.subscribeRepos`
277
278### Other
279
280- [x] `com.atproto.label.queryLabels`
281- [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS)
282- [x] `app.bsky.actor.getPreferences`
283- [x] `app.bsky.actor.putPreferences`
284
285## License
286
287This project is licensed under MIT license. `server/static/pico.css` is also licensed under MIT license, available at [https://github.com/picocss/pico/](https://github.com/picocss/pico/).