A community based topic aggregation platform built on atproto
1# Coves Production Stack
2#
3# Architecture:
4# - coves.social: AppView domain (API, frontend, .well-known/did.json)
5# - coves.me: PDS domain (must be separate from AppView)
6#
7# Hardware: AMD Epyc 7351p (16c/32t), 256GB RAM, 2x500GB NVMe RAID
8#
9# Usage:
10# docker-compose -f docker-compose.prod.yml up -d
11#
12# Prerequisites:
13# 1. DNS configured for both domains
14# 2. SSL certificates (Caddy handles this automatically)
15# 3. .env.prod file with secrets
16# 4. .well-known/did.json deployed to coves.social
17
18services:
19 # PostgreSQL Database for AppView
20 postgres:
21 image: postgres:15
22 container_name: coves-prod-postgres
23 restart: unless-stopped
24 environment:
25 POSTGRES_DB: ${POSTGRES_DB}
26 POSTGRES_USER: ${POSTGRES_USER}
27 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
28 volumes:
29 - postgres-data:/var/lib/postgresql/data
30 # Mount backup directory for pg_dump
31 - ./backups:/backups
32 networks:
33 - coves-internal
34 healthcheck:
35 test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
36 interval: 10s
37 timeout: 5s
38 retries: 5
39 # Generous limits for 256GB server
40 deploy:
41 resources:
42 limits:
43 memory: 32G
44 reservations:
45 memory: 4G
46
47 # Coves AppView (Go Server)
48 appview:
49 build:
50 context: .
51 dockerfile: Dockerfile
52 image: coves/appview:${VERSION:-latest}
53 container_name: coves-prod-appview
54 restart: unless-stopped
55 ports:
56 - "127.0.0.1:8080:8080" # Only expose to localhost (Caddy proxies)
57 environment:
58 # Database
59 DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable
60
61 # Instance identity
62 INSTANCE_DID: did:web:coves.social
63 INSTANCE_DOMAIN: coves.social
64
65 # PDS connection (separate domain!)
66 PDS_URL: https://coves.me
67
68 # Jetstream (Bluesky production firehose)
69 JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe
70
71 # Custom lexicon consumers (use production Jetstream with collection filters)
72 COMMUNITY_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.community.profile&wantedCollections=social.coves.community.subscription
73 POST_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.community.post
74 AGGREGATOR_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.aggregator.service&wantedCollections=social.coves.aggregator.authorization
75 VOTE_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.feed.vote
76 COMMENT_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.community.comment
77
78 # Security - MUST be false in production
79 AUTH_SKIP_VERIFY: "false"
80 SKIP_DID_WEB_VERIFICATION: "false"
81
82 # OAuth (for community account provisioning)
83 OAUTH_CLIENT_ID: ${OAUTH_CLIENT_ID}
84 OAUTH_REDIRECT_URI: ${OAUTH_REDIRECT_URI}
85 OAUTH_PRIVATE_JWK: ${OAUTH_PRIVATE_JWK}
86
87 # Application settings
88 PORT: 8080
89 ENV: production
90 LOG_LEVEL: info
91
92 # Encryption key for community credentials
93 ENCRYPTION_KEY: ${ENCRYPTION_KEY}
94
95 # Cursor encryption for pagination
96 CURSOR_SECRET: ${CURSOR_SECRET}
97 networks:
98 - coves-internal
99 depends_on:
100 postgres:
101 condition: service_healthy
102 healthcheck:
103 test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/xrpc/_health"]
104 interval: 30s
105 timeout: 5s
106 retries: 3
107 start_period: 10s
108 # Go is memory-efficient, but give it room for connection pools
109 deploy:
110 resources:
111 limits:
112 memory: 8G
113 reservations:
114 memory: 512M
115
116 # Bluesky PDS (Personal Data Server)
117 # Handles community accounts and their repositories
118 pds:
119 image: ghcr.io/bluesky-social/pds:latest
120 container_name: coves-prod-pds
121 restart: unless-stopped
122 ports:
123 - "127.0.0.1:3000:3000" # Only expose to localhost (Caddy proxies)
124 environment:
125 # PDS identity
126 PDS_HOSTNAME: coves.me
127 PDS_PORT: 3000
128 PDS_DATA_DIRECTORY: /pds
129 PDS_BLOBSTORE_DISK_LOCATION: /pds/blocks
130 PDS_BLOB_UPLOAD_LIMIT: 104857600 # 100 MB
131
132 # PLC Directory (production)
133 PDS_DID_PLC_URL: https://plc.directory
134
135 # Handle domains
136 # Community handles use @community.coves.social (AppView domain)
137 # Note: Root domain (coves.social) handle works via .well-known resolution
138 PDS_SERVICE_HANDLE_DOMAINS: .coves.social
139
140 # Security (set real values in .env.prod)
141 PDS_JWT_SECRET: ${PDS_JWT_SECRET}
142 PDS_ADMIN_PASSWORD: ${PDS_ADMIN_PASSWORD}
143 PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: ${PDS_ROTATION_KEY}
144
145 # Email (optional, for account recovery)
146 # NOTE: Must set BOTH or NEITHER - PDS fails with partial config
147 # PDS_EMAIL_SMTP_URL: ${PDS_EMAIL_SMTP_URL}
148 # PDS_EMAIL_FROM_ADDRESS: ${PDS_EMAIL_FROM_ADDRESS}
149
150 # Production mode
151 PDS_DEV_MODE: "false"
152 PDS_INVITE_REQUIRED: "false" # Set to true if you want invite-only
153
154 # Logging
155 NODE_ENV: production
156 LOG_ENABLED: "true"
157 LOG_LEVEL: info
158
159 # AppView proxy (for app.bsky.* methods like getProfile, notifications, etc.)
160 PDS_BSKY_APP_VIEW_URL: https://api.bsky.app
161 PDS_BSKY_APP_VIEW_DID: did:web:api.bsky.app
162
163 # Report service (for reporting content)
164 PDS_REPORT_SERVICE_URL: https://mod.bsky.app
165 PDS_REPORT_SERVICE_DID: did:plc:ar7c4by46qjdydhdevvrndac
166
167 # Relay crawlers (for federation with Bluesky network)
168 PDS_CRAWLERS: https://bsky.network
169 volumes:
170 - pds-data:/pds
171 networks:
172 - coves-internal
173 healthcheck:
174 test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/xrpc/_health"]
175 interval: 30s
176 timeout: 5s
177 retries: 5
178 # PDS (Node.js) needs memory for blob handling
179 deploy:
180 resources:
181 limits:
182 memory: 16G
183 reservations:
184 memory: 1G
185
186 # Caddy Reverse Proxy
187 # Handles HTTPS automatically via Let's Encrypt
188 # Uses Cloudflare plugin for wildcard SSL certificates (*.coves.social)
189 caddy:
190 # Pre-built Caddy with Cloudflare DNS plugin
191 # Updates automatically with docker-compose pull
192 # Alternative: build your own with Dockerfile.caddy
193 image: ghcr.io/slothcroissant/caddy-cloudflaredns:latest
194 container_name: coves-prod-caddy
195 restart: unless-stopped
196 ports:
197 - "80:80"
198 - "443:443"
199 environment:
200 # Required for wildcard SSL via DNS challenge
201 # Create at: Cloudflare Dashboard → My Profile → API Tokens → Create Token
202 # Permissions: Zone:DNS:Edit for coves.social zone
203 CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
204 volumes:
205 - ./Caddyfile:/etc/caddy/Caddyfile:ro
206 - caddy-data:/data
207 - caddy-config:/config
208 # Static files (.well-known, client-metadata.json, oauth callback)
209 - ./static:/srv:ro
210 networks:
211 - coves-internal
212 depends_on:
213 - appview
214 - pds
215
216networks:
217 coves-internal:
218 driver: bridge
219 name: coves-prod-network
220
221volumes:
222 postgres-data:
223 name: coves-prod-postgres-data
224 pds-data:
225 name: coves-prod-pds-data
226 caddy-data:
227 name: coves-prod-caddy-data
228 caddy-config:
229 name: coves-prod-caddy-config