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