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