A community based topic aggregation platform built on atproto
at main 277 lines 10 kB view raw
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 APPVIEW_PUBLIC_URL: https://coves.social 66 67 # PDS connection (separate domain!) 68 PDS_URL: https://coves.me 69 70 # Jetstream (Bluesky production firehose) 71 JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe 72 73 # Custom lexicon consumers (use production Jetstream with collection filters) 74 COMMUNITY_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.community.profile&wantedCollections=social.coves.community.subscription 75 POST_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.community.post 76 AGGREGATOR_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.aggregator.service&wantedCollections=social.coves.aggregator.authorization 77 VOTE_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.feed.vote 78 COMMENT_JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=social.coves.community.comment 79 80 # Security - MUST be false in production 81 AUTH_SKIP_VERIFY: "false" 82 SKIP_DID_WEB_VERIFICATION: "false" 83 84 # OAuth (for community account provisioning) 85 OAUTH_CLIENT_ID: ${OAUTH_CLIENT_ID} 86 OAUTH_REDIRECT_URI: ${OAUTH_REDIRECT_URI} 87 OAUTH_SEAL_SECRET: ${OAUTH_SEAL_SECRET} 88 89 # OAuth Confidential Client (optional - enables 1-year sessions instead of 14-day) 90 # Generate keys with: go run ./cmd/tools/generate-oauth-key 91 OAUTH_CLIENT_PRIVATE_KEY: ${OAUTH_CLIENT_PRIVATE_KEY:-} 92 OAUTH_CLIENT_KEY_ID: ${OAUTH_CLIENT_KEY_ID:-} 93 94 # Application settings 95 PORT: 8080 96 ENV: production 97 LOG_LEVEL: info 98 99 # Encryption key for community credentials 100 ENCRYPTION_KEY: ${ENCRYPTION_KEY} 101 102 # Cursor encryption for pagination 103 CURSOR_SECRET: ${CURSOR_SECRET} 104 105 # PDS JWT secret for verifying HS256 tokens from the PDS 106 # Must match the PDS_JWT_SECRET configured on the PDS 107 PDS_JWT_SECRET: ${PDS_JWT_SECRET} 108 # Whitelist PDS issuer(s) allowed to use HS256 (no kid) 109 HS256_ISSUERS: ${HS256_ISSUERS} 110 111 # Restrict community creation to instance DID only 112 COMMUNITY_CREATORS: did:web:coves.social 113 114 # Trusted aggregators (bypass per-community authorization check) 115 # Comma-separated list of DIDs 116 TRUSTED_AGGREGATOR_DIDS: ${TRUSTED_AGGREGATOR_DIDS:-} 117 118 # Image proxy configuration (on-the-fly resizing with disk cache) 119 IMAGE_PROXY_ENABLED: ${IMAGE_PROXY_ENABLED:-true} 120 IMAGE_PROXY_BASE_URL: ${IMAGE_PROXY_BASE_URL:-https://coves.social} 121 IMAGE_PROXY_CACHE_PATH: /var/cache/coves/images 122 IMAGE_PROXY_CACHE_MAX_GB: ${IMAGE_PROXY_CACHE_MAX_GB:-10} 123 IMAGE_PROXY_FETCH_TIMEOUT_SECONDS: ${IMAGE_PROXY_FETCH_TIMEOUT_SECONDS:-30} 124 IMAGE_PROXY_MAX_SOURCE_SIZE_MB: ${IMAGE_PROXY_MAX_SOURCE_SIZE_MB:-10} 125 126 # OpenTelemetry Observability (optional - disabled by default) 127 OTEL_ENABLED: ${OTEL_ENABLED:-false} 128 OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-} 129 OTEL_EXPORTER_OTLP_HEADERS: ${OTEL_EXPORTER_OTLP_HEADERS:-} 130 OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME:-coves-appview} 131 OTEL_TRACES_SAMPLER_ARG: ${OTEL_TRACES_SAMPLER_ARG:-1.0} 132 OTEL_EXPORTER_OTLP_INSECURE: ${OTEL_EXPORTER_OTLP_INSECURE:-false} 133 volumes: 134 # Image proxy cache (persistent across container restarts) 135 - imageproxy-cache:/var/cache/coves/images 136 networks: 137 - coves-internal 138 depends_on: 139 postgres: 140 condition: service_healthy 141 healthcheck: 142 test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/xrpc/_health"] 143 interval: 30s 144 timeout: 5s 145 retries: 3 146 start_period: 10s 147 # Go is memory-efficient, but give it room for connection pools 148 deploy: 149 resources: 150 limits: 151 memory: 8G 152 reservations: 153 memory: 512M 154 155 # Bluesky PDS (Personal Data Server) 156 # Handles community accounts and their repositories 157 pds: 158 image: ghcr.io/bluesky-social/pds:latest 159 container_name: coves-prod-pds 160 restart: unless-stopped 161 ports: 162 - "127.0.0.1:3000:3000" # Only expose to localhost (Caddy proxies) 163 environment: 164 # PDS identity (use pds.coves.me for fresh relay registration) 165 PDS_HOSTNAME: pds.coves.me 166 PDS_PORT: 3000 167 PDS_DATA_DIRECTORY: /pds 168 PDS_BLOB_UPLOAD_LIMIT: 104857600 # 100 MB 169 170 # S3-compatible blob storage 171 PDS_BLOBSTORE_S3_BUCKET: ${PDS_S3_BUCKET} 172 PDS_BLOBSTORE_S3_REGION: ${PDS_S3_REGION} 173 PDS_BLOBSTORE_S3_ENDPOINT: ${PDS_S3_ENDPOINT} 174 PDS_BLOBSTORE_S3_ACCESS_KEY_ID: ${PDS_S3_ACCESS_KEY_ID} 175 PDS_BLOBSTORE_S3_SECRET_ACCESS_KEY: ${PDS_S3_SECRET_ACCESS_KEY} 176 PDS_BLOBSTORE_S3_FORCE_PATH_STYLE: "true" 177 178 # PLC Directory (production) 179 PDS_DID_PLC_URL: https://plc.directory 180 181 # Handle domains 182 # Community handles use @community.coves.social (AppView domain) 183 # Note: Root domain (coves.social) handle works via .well-known resolution 184 PDS_SERVICE_HANDLE_DOMAINS: .coves.social 185 186 # Security (set real values in .env.prod) 187 PDS_JWT_SECRET: ${PDS_JWT_SECRET} 188 PDS_ADMIN_PASSWORD: ${PDS_ADMIN_PASSWORD} 189 PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX: ${PDS_ROTATION_KEY} 190 191 # Email (optional, for account recovery) 192 # NOTE: Must set BOTH or NEITHER - PDS fails with partial config 193 # PDS_EMAIL_SMTP_URL: ${PDS_EMAIL_SMTP_URL} 194 # PDS_EMAIL_FROM_ADDRESS: ${PDS_EMAIL_FROM_ADDRESS} 195 196 # Production mode 197 PDS_DEV_MODE: "false" 198 PDS_INVITE_REQUIRED: "true" 199 200 # Logging 201 NODE_ENV: production 202 LOG_ENABLED: "true" 203 LOG_LEVEL: info 204 205 # AppView proxy (for app.bsky.* methods like getProfile, notifications, etc.) 206 PDS_BSKY_APP_VIEW_URL: https://api.bsky.app 207 PDS_BSKY_APP_VIEW_DID: did:web:api.bsky.app 208 209 # Report service (for reporting content) 210 PDS_REPORT_SERVICE_URL: https://mod.bsky.app 211 PDS_REPORT_SERVICE_DID: did:plc:ar7c4by46qjdydhdevvrndac 212 213 # Relay crawlers (for federation with Bluesky network) 214 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 215 volumes: 216 - pds-data:/pds 217 networks: 218 - coves-internal 219 healthcheck: 220 test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/xrpc/_health"] 221 interval: 30s 222 timeout: 5s 223 retries: 5 224 # PDS (Node.js) needs memory for blob handling 225 deploy: 226 resources: 227 limits: 228 memory: 16G 229 reservations: 230 memory: 1G 231 232 # Caddy Reverse Proxy 233 # Handles HTTPS automatically via Let's Encrypt 234 # Uses Cloudflare plugin for wildcard SSL certificates (*.coves.social) 235 caddy: 236 # Pre-built Caddy with Cloudflare DNS plugin 237 # Updates automatically with docker-compose pull 238 # Alternative: build your own with Dockerfile.caddy 239 image: ghcr.io/slothcroissant/caddy-cloudflaredns:latest 240 container_name: coves-prod-caddy 241 restart: unless-stopped 242 ports: 243 - "80:80" 244 - "443:443" 245 environment: 246 # Required for wildcard SSL via DNS challenge 247 # Create at: Cloudflare Dashboard → My Profile → API Tokens → Create Token 248 # Permissions: Zone:DNS:Edit for coves.social zone 249 CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN} 250 volumes: 251 - ./Caddyfile:/etc/caddy/Caddyfile:ro 252 - caddy-data:/data 253 - caddy-config:/config 254 # Static files (.well-known, client-metadata.json, oauth callback) 255 - ./static:/srv:ro 256 networks: 257 - coves-internal 258 depends_on: 259 - appview 260 - pds 261 262networks: 263 coves-internal: 264 driver: bridge 265 name: coves-prod-network 266 267volumes: 268 postgres-data: 269 name: coves-prod-postgres-data 270 pds-data: 271 name: coves-prod-pds-data 272 caddy-data: 273 name: coves-prod-caddy-data 274 caddy-config: 275 name: coves-prod-caddy-config 276 imageproxy-cache: 277 name: coves-prod-imageproxy-cache