services: traefik: image: traefik:v3.6 restart: unless-stopped ports: - "80:80" - "443:443" command: - --api.dashboard=false - --providers.docker=true - --providers.docker.exposedbydefault=false - --providers.docker.network=prod - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --certificatesresolvers.cloudflare.acme.dnschallenge=true - --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare - --certificatesresolvers.cloudflare.acme.email=$${ACME_EMAIL} - --certificatesresolvers.cloudflare.acme.storage=/letsencrypt/acme.json environment: - CF_DNS_API_TOKEN=$${CF_DNS_API_TOKEN} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik-certs:/letsencrypt networks: - prod imaginary: image: h2non/imaginary:latest restart: unless-stopped command: -enable-url-source=false -max-allowed-size=15728640 networks: - prod postgres: image: postgres:16-alpine restart: unless-stopped volumes: - postgres-data:/var/lib/postgresql/data environment: - POSTGRES_DB=writekit - POSTGRES_USER=writekit - POSTGRES_PASSWORD=$${PG_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U writekit"] interval: 5s timeout: 5s retries: 5 networks: - prod oauth2-proxy: image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1 restart: unless-stopped environment: - OAUTH2_PROXY_PROVIDER=github - OAUTH2_PROXY_CLIENT_ID=$${GITHUB_CLIENT_ID_STAGING} - OAUTH2_PROXY_CLIENT_SECRET=$${GITHUB_CLIENT_SECRET_STAGING} - OAUTH2_PROXY_COOKIE_SECRET=$${OAUTH2_PROXY_COOKIE_SECRET} - OAUTH2_PROXY_COOKIE_DOMAINS=.$${DOMAIN} - OAUTH2_PROXY_EMAIL_DOMAINS=* - OAUTH2_PROXY_GITHUB_USERS=$${OAUTH2_PROXY_ALLOWED_USERS} - OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 - OAUTH2_PROXY_REVERSE_PROXY=true - OAUTH2_PROXY_SET_XAUTHREQUEST=true - OAUTH2_PROXY_PASS_ACCESS_TOKEN=true labels: - traefik.enable=true - traefik.http.routers.oauth2-proxy.rule=Host(`auth.staging.$${DOMAIN}`) - traefik.http.routers.oauth2-proxy.tls=true - traefik.http.routers.oauth2-proxy.tls.certresolver=cloudflare - traefik.http.services.oauth2-proxy.loadbalancer.server.port=4180 - traefik.http.middlewares.staging-auth.forwardauth.address=http://oauth2-proxy:4180/oauth2/auth - traefik.http.middlewares.staging-auth.forwardauth.trustForwardHeader=true - traefik.http.middlewares.staging-auth.forwardauth.authResponseHeaders=X-Auth-Request-User,X-Auth-Request-Email networks: - prod writekit-prod: image: $${REGISTRY_URL}/writekit:main restart: unless-stopped environment: - ENV=prod - DOMAIN=$${DOMAIN} - BASE_URL=https://$${DOMAIN} - DATABASE_URL=postgres://writekit:$${PG_PASSWORD}@postgres:5432/writekit?sslmode=disable - DATA_DIR=/data - R2_ACCOUNT_ID=$${R2_ACCOUNT_ID} - R2_ACCESS_KEY_ID=$${R2_ACCESS_KEY_ID} - R2_SECRET_ACCESS_KEY=$${R2_SECRET_ACCESS_KEY} - R2_BUCKET=$${R2_BUCKET} - R2_PUBLIC_URL=$${R2_PUBLIC_URL} - GITHUB_CLIENT_ID=$${GITHUB_CLIENT_ID} - GITHUB_CLIENT_SECRET=$${GITHUB_CLIENT_SECRET} - GOOGLE_CLIENT_ID=$${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=$${GOOGLE_CLIENT_SECRET} - DISCORD_CLIENT_ID=$${DISCORD_CLIENT_ID} - DISCORD_CLIENT_SECRET=$${DISCORD_CLIENT_SECRET} - SESSION_SECRET=$${SESSION_SECRET} - LEMON_API_KEY=$${LEMON_API_KEY} - LEMON_STORE_ID=$${LEMON_STORE_ID} - LEMON_WEBHOOK_SECRET=$${LEMON_WEBHOOK_SECRET} - WISE_API_KEY=$${WISE_API_KEY} - WISE_PROFILE_ID=$${WISE_PROFILE_ID} - IMAGINARY_URL=http://imaginary:9000 volumes: - tenants-prod:/data labels: - traefik.enable=true - traefik.http.routers.writekit-prod-platform.rule=Host(`$${DOMAIN}`) - traefik.http.routers.writekit-prod-platform.tls=true - traefik.http.routers.writekit-prod-platform.tls.certresolver=cloudflare - traefik.http.routers.writekit-prod-platform.service=writekit-prod - traefik.http.routers.writekit-prod-blogs.rule=HostRegexp(`^(?!staging\.).+\.$${DOMAIN}$$`) - traefik.http.routers.writekit-prod-blogs.priority=10 - traefik.http.routers.writekit-prod-blogs.tls=true - traefik.http.routers.writekit-prod-blogs.tls.certresolver=cloudflare - traefik.http.routers.writekit-prod-blogs.tls.domains[0].main=$${DOMAIN} - traefik.http.routers.writekit-prod-blogs.tls.domains[0].sans=*.$${DOMAIN} - traefik.http.routers.writekit-prod-blogs.service=writekit-prod - traefik.http.services.writekit-prod.loadbalancer.server.port=8080 depends_on: postgres: condition: service_healthy networks: - prod writekit-staging: image: $${REGISTRY_URL}/writekit:dev restart: unless-stopped environment: - ENV=staging - DOMAIN=staging.$${DOMAIN} - BASE_URL=https://staging.$${DOMAIN} - DATABASE_URL=postgres://writekit:$${PG_PASSWORD}@postgres:5432/writekit_staging?sslmode=disable - DATA_DIR=/data - R2_ACCOUNT_ID=$${R2_ACCOUNT_ID} - R2_ACCESS_KEY_ID=$${R2_ACCESS_KEY_ID} - R2_SECRET_ACCESS_KEY=$${R2_SECRET_ACCESS_KEY} - R2_BUCKET=$${R2_BUCKET} - R2_PUBLIC_URL=$${R2_PUBLIC_URL} - GITHUB_CLIENT_ID=$${GITHUB_CLIENT_ID_STAGING} - GITHUB_CLIENT_SECRET=$${GITHUB_CLIENT_SECRET_STAGING} - GOOGLE_CLIENT_ID=$${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=$${GOOGLE_CLIENT_SECRET} - DISCORD_CLIENT_ID=$${DISCORD_CLIENT_ID} - DISCORD_CLIENT_SECRET=$${DISCORD_CLIENT_SECRET} - SESSION_SECRET=$${SESSION_SECRET_STAGING} - LEMON_API_KEY=$${LEMON_API_KEY_STAGING} - LEMON_STORE_ID=$${LEMON_STORE_ID} - LEMON_WEBHOOK_SECRET=$${LEMON_WEBHOOK_SECRET_STAGING} - WISE_API_KEY=$${WISE_API_KEY_STAGING} - WISE_PROFILE_ID=$${WISE_PROFILE_ID_STAGING} - IMAGINARY_URL=http://imaginary:9000 volumes: - tenants-staging:/data labels: - traefik.enable=true - traefik.http.routers.writekit-staging-platform.rule=Host(`staging.$${DOMAIN}`) - traefik.http.routers.writekit-staging-platform.tls=true - traefik.http.routers.writekit-staging-platform.tls.certresolver=cloudflare - traefik.http.routers.writekit-staging-platform.middlewares=staging-auth - traefik.http.routers.writekit-staging-platform.service=writekit-staging - traefik.http.routers.writekit-staging-blogs.rule=HostRegexp(`^.+\.staging\.$${DOMAIN}$$`) - traefik.http.routers.writekit-staging-blogs.priority=20 - traefik.http.routers.writekit-staging-blogs.tls=true - traefik.http.routers.writekit-staging-blogs.tls.certresolver=cloudflare - traefik.http.routers.writekit-staging-blogs.tls.domains[0].main=staging.$${DOMAIN} - traefik.http.routers.writekit-staging-blogs.tls.domains[0].sans=*.staging.$${DOMAIN} - traefik.http.routers.writekit-staging-blogs.middlewares=staging-auth - traefik.http.routers.writekit-staging-blogs.service=writekit-staging - traefik.http.services.writekit-staging.loadbalancer.server.port=8080 depends_on: postgres: condition: service_healthy networks: - prod networks: prod: volumes: traefik-certs: postgres-data: tenants-prod: tenants-staging: