docker-compose.yml 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. x-logging: &default-logging
  2. driver: json-file
  3. options:
  4. max-size: "10m"
  5. max-file: "5"
  6. services:
  7. # ─── Next.js Application ──────────────────────────────────────────
  8. app:
  9. build:
  10. context: .
  11. dockerfile: Dockerfile
  12. restart: unless-stopped
  13. logging: *default-logging
  14. depends_on:
  15. supabase-kong:
  16. condition: service_started
  17. environment:
  18. - TMDB_API_KEY=${TMDB_API_KEY}
  19. - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
  20. - NEXT_PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
  21. - SUPABASE_INTERNAL_URL=http://supabase-kong:8000
  22. - SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
  23. - MASTER_ADMIN_USERNAME=${MASTER_ADMIN_USERNAME}
  24. - MASTER_ADMIN_TOTP_SECRET=${MASTER_ADMIN_TOTP_SECRET}
  25. - IRON_SESSION_SECRET=${IRON_SESSION_SECRET}
  26. - JWT_SECRET=${JWT_SECRET}
  27. - RECOVERY_CODE_PEPPER=${RECOVERY_CODE_PEPPER}
  28. networks:
  29. - internal
  30. # ─── Caddy Reverse Proxy ──────────────────────────────────────────
  31. caddy:
  32. image: caddy:2-alpine
  33. restart: unless-stopped
  34. logging: *default-logging
  35. ports:
  36. - "80:80"
  37. - "443:443"
  38. - "443:443/udp"
  39. environment:
  40. - DOMAIN=${DOMAIN:-localhost}
  41. - TLS_EMAIL=${TLS_EMAIL:-}
  42. volumes:
  43. - ./Caddyfile:/etc/caddy/Caddyfile:ro
  44. - caddy_data:/data
  45. - caddy_config:/config
  46. depends_on:
  47. - app
  48. networks:
  49. - internal
  50. # ─── Supabase: Postgres ───────────────────────────────────────────
  51. supabase-db:
  52. image: supabase/postgres:15.8.1.060
  53. restart: unless-stopped
  54. logging: *default-logging
  55. healthcheck:
  56. test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-supabase_admin} -d ${POSTGRES_DB:-supabase}"]
  57. interval: 10s
  58. timeout: 5s
  59. retries: 5
  60. environment:
  61. POSTGRES_USER: ${POSTGRES_USER:-supabase_admin}
  62. POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
  63. POSTGRES_DB: ${POSTGRES_DB:-supabase}
  64. JWT_SECRET: ${JWT_SECRET}
  65. volumes:
  66. - supabase_db:/var/lib/postgresql/data
  67. networks:
  68. - internal
  69. # ─── Supabase: GoTrue (Auth) ──────────────────────────────────────
  70. supabase-auth:
  71. # pinned for supabase/auth#2013 workaround — see /home/user/.claude/plans/exactly-yes-precious-knuth.md
  72. image: supabase/gotrue:v2.170.0
  73. restart: unless-stopped
  74. logging: *default-logging
  75. depends_on:
  76. supabase-db:
  77. condition: service_healthy
  78. environment:
  79. GOTRUE_API_HOST: "0.0.0.0"
  80. GOTRUE_API_PORT: "9999"
  81. API_EXTERNAL_URL: ${NEXT_PUBLIC_SUPABASE_URL}
  82. GOTRUE_DB_DRIVER: postgres
  83. GOTRUE_DB_DATABASE_URL: postgres://${POSTGRES_USER:-supabase_admin}:${POSTGRES_PASSWORD}@supabase-db:5432/${POSTGRES_DB:-supabase}?search_path=auth
  84. GOTRUE_SITE_URL: ${SITE_URL:-http://localhost:3000}
  85. GOTRUE_URI_ALLOW_LIST: ""
  86. # Paired with GOTRUE_EXTERNAL_EMAIL_ENABLED below: the recovery
  87. # synthetic-identity design (<uid>@moviedice.invalid + HKDF password)
  88. # requires signInWithPassword, so email login must be on. Public signups
  89. # are disabled so the email path stays admin-only (admin.updateUserById
  90. # in /api/auth/recovery/generate).
  91. GOTRUE_DISABLE_SIGNUP: "true"
  92. GOTRUE_JWT_SECRET: ${JWT_SECRET}
  93. GOTRUE_JWT_EXP: "3600"
  94. GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
  95. # Anonymous auth enabled — core requirement
  96. GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: "true"
  97. # Disable all other auth methods
  98. GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
  99. GOTRUE_EXTERNAL_PHONE_ENABLED: "false"
  100. GOTRUE_MAILER_AUTOCONFIRM: "false"
  101. GOTRUE_SMS_AUTOCONFIRM: "false"
  102. networks:
  103. - internal
  104. # ─── Supabase: PostgREST ──────────────────────────────────────────
  105. supabase-rest:
  106. image: postgrest/postgrest:v12.2.8
  107. restart: unless-stopped
  108. logging: *default-logging
  109. depends_on:
  110. supabase-db:
  111. condition: service_healthy
  112. environment:
  113. PGRST_DB_URI: postgres://${POSTGRES_USER:-supabase_admin}:${POSTGRES_PASSWORD}@supabase-db:5432/${POSTGRES_DB:-supabase}
  114. PGRST_DB_SCHEMAS: public
  115. PGRST_DB_ANON_ROLE: anon
  116. PGRST_JWT_SECRET: ${JWT_SECRET}
  117. PGRST_DB_USE_LEGACY_GUCS: "false"
  118. networks:
  119. - internal
  120. # ─── Supabase: Realtime ───────────────────────────────────────────
  121. supabase-realtime:
  122. image: supabase/realtime:v2.34.47
  123. restart: unless-stopped
  124. logging: *default-logging
  125. depends_on:
  126. supabase-db:
  127. condition: service_healthy
  128. environment:
  129. PORT: "4000"
  130. DB_HOST: supabase-db
  131. DB_PORT: "5432"
  132. DB_USER: ${POSTGRES_USER:-supabase_admin}
  133. DB_PASSWORD: ${POSTGRES_PASSWORD}
  134. DB_NAME: ${POSTGRES_DB:-supabase}
  135. DB_AFTER_CONNECT_QUERY: "SET search_path TO _realtime"
  136. DB_ENC_KEY: supabaserealtime
  137. API_JWT_SECRET: ${JWT_SECRET}
  138. SECRET_KEY_BASE: ${REALTIME_SECRET_KEY_BASE:-please-generate-a-64-char-secret-key-base-for-realtime-service!!}
  139. ERL_AFLAGS: "-proto_dist inet_tcp"
  140. DNS_NODES: "''"
  141. RLIMIT_NOFILE: "10000"
  142. APP_NAME: realtime
  143. SEED_SELF_HOST: "true"
  144. networks:
  145. - internal
  146. # ─── Supabase: Kong (API Gateway) ─────────────────────────────────
  147. supabase-kong:
  148. image: kong:2.8.1
  149. restart: unless-stopped
  150. logging: *default-logging
  151. depends_on:
  152. - supabase-auth
  153. - supabase-rest
  154. - supabase-realtime
  155. environment:
  156. KONG_DATABASE: "off"
  157. KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
  158. KONG_DNS_ORDER: LAST,A,CNAME
  159. KONG_PLUGINS: request-transformer,cors,key-auth,acl
  160. KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
  161. KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
  162. ANON_KEY: ${ANON_KEY}
  163. SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
  164. volumes:
  165. - ./supabase/kong/kong.yml:/var/lib/kong/kong.yml:ro
  166. networks:
  167. - internal
  168. # ─── Supabase: Studio (dev only) ──────────────────────────────────
  169. supabase-studio:
  170. image: supabase/studio:20250317-b9tried
  171. restart: unless-stopped
  172. logging: *default-logging
  173. ports:
  174. - "127.0.0.1:3001:3000"
  175. depends_on:
  176. - supabase-kong
  177. environment:
  178. STUDIO_PG_META_URL: http://supabase-meta:8080
  179. POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
  180. SUPABASE_URL: http://supabase-kong:8000
  181. SUPABASE_REST_URL: http://supabase-kong:8000/rest/v1/
  182. SUPABASE_ANON_KEY: ${ANON_KEY}
  183. SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
  184. DEFAULT_ORGANIZATION_NAME: MovieDice
  185. DEFAULT_PROJECT_NAME: MovieDice
  186. NEXT_PUBLIC_ENABLE_LOGS: "true"
  187. NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
  188. networks:
  189. - internal
  190. # ─── Supabase: pg_meta (required by Studio) ───────────────────────
  191. supabase-meta:
  192. image: supabase/postgres-meta:v0.84.2
  193. restart: unless-stopped
  194. logging: *default-logging
  195. depends_on:
  196. supabase-db:
  197. condition: service_healthy
  198. environment:
  199. PG_META_PORT: "8080"
  200. PG_META_DB_HOST: supabase-db
  201. PG_META_DB_PORT: "5432"
  202. PG_META_DB_NAME: ${POSTGRES_DB:-supabase}
  203. PG_META_DB_USER: ${POSTGRES_USER:-supabase_admin}
  204. PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
  205. networks:
  206. - internal
  207. # ─── Cron Service ─────────────────────────────────────────────────
  208. cron:
  209. build:
  210. context: ./cron
  211. dockerfile: Dockerfile
  212. restart: unless-stopped
  213. logging: *default-logging
  214. depends_on:
  215. supabase-kong:
  216. condition: service_started
  217. environment:
  218. - SUPABASE_URL=http://supabase-kong:8000
  219. - SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
  220. - TMDB_API_KEY=${TMDB_API_KEY}
  221. networks:
  222. - internal
  223. # ─── Database Backup ──────────────────────────────────────────────
  224. backup:
  225. image: postgres:15-alpine
  226. restart: "no"
  227. logging: *default-logging
  228. depends_on:
  229. supabase-db:
  230. condition: service_healthy
  231. environment:
  232. POSTGRES_USER: ${POSTGRES_USER:-supabase_admin}
  233. POSTGRES_DB: ${POSTGRES_DB:-supabase}
  234. PGPASSWORD: ${POSTGRES_PASSWORD}
  235. volumes:
  236. - ./backup/backup.sh:/backup.sh:ro
  237. - supabase_backups:/backups
  238. entrypoint: ["/bin/sh", "/backup.sh"]
  239. networks:
  240. - internal
  241. profiles:
  242. - backup
  243. volumes:
  244. supabase_db:
  245. supabase_backups:
  246. caddy_data:
  247. caddy_config:
  248. networks:
  249. internal:
  250. driver: bridge