1# Cocoon
2
3> [!WARNING]
4I 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
159**Blob Storage Options:**
160- `COCOON_S3_BLOBSTORE_ENABLED=false` (default): Blobs stored in the database
161- `COCOON_S3_BLOBSTORE_ENABLED=true`: Blobs stored in S3 bucket under `blobs/{did}/{cid}`
162
163### Management Commands
164
165Create an invite code:
166```bash
167docker exec cocoon-pds /cocoon create-invite-code --uses 1
168```
169
170Reset a user's password:
171```bash
172docker exec cocoon-pds /cocoon reset-password --did "did:plc:xxx"
173```
174
175### Updating
176
177```bash
178docker-compose pull
179docker-compose up -d
180```
181
182## Implemented Endpoints
183
184> [!NOTE]
185Just 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.
186
187### Identity
188
189- [x] `com.atproto.identity.getRecommendedDidCredentials`
190- [x] `com.atproto.identity.requestPlcOperationSignature`
191- [x] `com.atproto.identity.resolveHandle`
192- [x] `com.atproto.identity.signPlcOperation`
193- [x] `com.atproto.identity.submitPlcOperation`
194- [x] `com.atproto.identity.updateHandle`
195
196### Repo
197
198- [x] `com.atproto.repo.applyWrites`
199- [x] `com.atproto.repo.createRecord`
200- [x] `com.atproto.repo.putRecord`
201- [x] `com.atproto.repo.deleteRecord`
202- [x] `com.atproto.repo.describeRepo`
203- [x] `com.atproto.repo.getRecord`
204- [x] `com.atproto.repo.importRepo` (Works "okay". Use with extreme caution.)
205- [x] `com.atproto.repo.listRecords`
206- [x] `com.atproto.repo.listMissingBlobs` (Not actually functional, but will return a response as if no blobs were missing)
207
208### Server
209
210- [x] `com.atproto.server.activateAccount`
211- [x] `com.atproto.server.checkAccountStatus`
212- [x] `com.atproto.server.confirmEmail`
213- [x] `com.atproto.server.createAccount`
214- [x] `com.atproto.server.createInviteCode`
215- [x] `com.atproto.server.createInviteCodes`
216- [x] `com.atproto.server.deactivateAccount`
217- [ ] `com.atproto.server.deleteAccount`
218- [x] `com.atproto.server.deleteSession`
219- [x] `com.atproto.server.describeServer`
220- [ ] `com.atproto.server.getAccountInviteCodes`
221- [ ] `com.atproto.server.getServiceAuth`
222- ~~[ ] `com.atproto.server.listAppPasswords`~~ - not going to add app passwords
223- [x] `com.atproto.server.refreshSession`
224- [ ] `com.atproto.server.requestAccountDelete`
225- [x] `com.atproto.server.requestEmailConfirmation`
226- [x] `com.atproto.server.requestEmailUpdate`
227- [x] `com.atproto.server.requestPasswordReset`
228- [ ] `com.atproto.server.reserveSigningKey`
229- [x] `com.atproto.server.resetPassword`
230- ~~[] `com.atproto.server.revokeAppPassword`~~ - not going to add app passwords
231- [x] `com.atproto.server.updateEmail`
232
233### Sync
234
235- [x] `com.atproto.sync.getBlob`
236- [x] `com.atproto.sync.getBlocks`
237- [x] `com.atproto.sync.getLatestCommit`
238- [x] `com.atproto.sync.getRecord`
239- [x] `com.atproto.sync.getRepoStatus`
240- [x] `com.atproto.sync.getRepo`
241- [x] `com.atproto.sync.listBlobs`
242- [x] `com.atproto.sync.listRepos`
243- ~~[ ] `com.atproto.sync.notifyOfUpdate`~~ - BGS doesn't even have this implemented lol
244- [x] `com.atproto.sync.requestCrawl`
245- [x] `com.atproto.sync.subscribeRepos`
246
247### Other
248
249- [ ] `com.atproto.label.queryLabels`
250- [x] `com.atproto.moderation.createReport` (Note: this should be handled by proxying, not actually implemented in the PDS)
251- [x] `app.bsky.actor.getPreferences`
252- [x] `app.bsky.actor.putPreferences`
253
254## License
255
256This 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/).