From 3c986f17090b275e413d6197116942a3d242e1ac Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sun, 22 Feb 2026 03:07:09 +0000 Subject: [PATCH] Add standalone docker-compose for all 20 modules Each module can run as an independent container using its standalone.ts entrypoint. Reuses the same rspace-online image with CMD override. Usage: docker compose -f docker-compose.yml -f docker-compose.standalone.yml \ up -d rtrips-standalone All services share rspace-db and traefik-public. Module-specific deps mapped: books/files/swag volumes, OSRM for trips, IMAP for inbox, R2 for tube, payment network for cart/funds. Co-Authored-By: Claude Opus 4.6 --- docker-compose.standalone.yml | 327 ++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 docker-compose.standalone.yml diff --git a/docker-compose.standalone.yml b/docker-compose.standalone.yml new file mode 100644 index 0000000..2be9904 --- /dev/null +++ b/docker-compose.standalone.yml @@ -0,0 +1,327 @@ +# Standalone module deployments — each module runs as its own container. +# +# Usage: +# docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -d rbooks-standalone +# +# All services reuse the same rspace-online image (built by the main compose). +# They share the rspace-db database and traefik-public network. +# To deploy ALL standalone modules at once: +# docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -d +# +# NOTE: Standalone services override the default CMD to run standalone.ts. +# The unified rspace-online container still serves all domains by default. +# To switch a domain from unified to standalone, remove its Traefik label +# from the main compose and bring up its standalone service here. + +x-standalone-base: &standalone-base + image: rspace-online-rspace:latest + restart: unless-stopped + environment: &base-env + NODE_ENV: production + PORT: "3000" + DATABASE_URL: postgres://rspace:${POSTGRES_PASSWORD:-rspace}@rspace-db:5432/rspace + depends_on: + rspace-db: + condition: service_healthy + networks: + - traefik-public + - rspace-internal + +x-traefik-labels: &traefik-enabled + traefik.enable: "true" + +services: + # ── rBooks ── + rbooks-standalone: + <<: *standalone-base + container_name: rbooks-standalone + command: ["bun", "run", "modules/books/standalone.ts"] + volumes: + - rspace-books:/data/books + environment: + <<: *base-env + BOOKS_DIR: /data/books + labels: + <<: *traefik-enabled + traefik.http.routers.rbooks-sa.rule: Host(`rbooks.online`) + traefik.http.routers.rbooks-sa.entrypoints: web + traefik.http.services.rbooks-sa.loadbalancer.server.port: "3000" + + # ── rPubs ── + rpubs-standalone: + <<: *standalone-base + container_name: rpubs-standalone + command: ["bun", "run", "modules/pubs/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rpubs-sa.rule: Host(`rpubs.online`) + traefik.http.routers.rpubs-sa.entrypoints: web + traefik.http.services.rpubs-sa.loadbalancer.server.port: "3000" + + # ── rCart ── + rcart-standalone: + <<: *standalone-base + container_name: rcart-standalone + command: ["bun", "run", "modules/cart/standalone.ts"] + environment: + <<: *base-env + FLOW_SERVICE_URL: http://payment-flow:3010 + FLOW_ID: a79144ec-e6a2-4e30-a42a-6d8237a5953d + FUNNEL_ID: 0ff6a9ac-1667-4fc7-9a01-b1620810509f + X402_PAY_TO: ${X402_PAY_TO:-} + X402_NETWORK: ${X402_NETWORK:-eip155:84532} + X402_UPLOAD_PRICE: ${X402_UPLOAD_PRICE:-0.01} + X402_FACILITATOR_URL: ${X402_FACILITATOR_URL:-https://x402.org/facilitator} + networks: + - traefik-public + - rspace-internal + - payment-network + labels: + <<: *traefik-enabled + traefik.http.routers.rcart-sa.rule: Host(`rcart.online`) + traefik.http.routers.rcart-sa.entrypoints: web + traefik.http.services.rcart-sa.loadbalancer.server.port: "3000" + + # ── rProviders ── + rproviders-standalone: + <<: *standalone-base + container_name: rproviders-standalone + command: ["bun", "run", "modules/providers/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rproviders-sa.rule: Host(`providers.mycofi.earth`) + traefik.http.routers.rproviders-sa.entrypoints: web + traefik.http.services.rproviders-sa.loadbalancer.server.port: "3000" + + # ── rSwag ── + rswag-standalone: + <<: *standalone-base + container_name: rswag-standalone + command: ["bun", "run", "modules/swag/standalone.ts"] + volumes: + - rspace-swag:/data/swag-artifacts + environment: + <<: *base-env + SWAG_ARTIFACTS_DIR: /data/swag-artifacts + labels: + <<: *traefik-enabled + traefik.http.routers.rswag-sa.rule: Host(`swag.mycofi.earth`) + traefik.http.routers.rswag-sa.entrypoints: web + traefik.http.services.rswag-sa.loadbalancer.server.port: "3000" + + # ── rChoices ── + rchoices-standalone: + <<: *standalone-base + container_name: rchoices-standalone + command: ["bun", "run", "modules/choices/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rchoices-sa.rule: Host(`rchoices.online`) + traefik.http.routers.rchoices-sa.entrypoints: web + traefik.http.services.rchoices-sa.loadbalancer.server.port: "3000" + + # ── rFunds ── + rfunds-standalone: + <<: *standalone-base + container_name: rfunds-standalone + command: ["bun", "run", "modules/funds/standalone.ts"] + environment: + <<: *base-env + FLOW_SERVICE_URL: http://payment-flow:3010 + FLOW_ID: a79144ec-e6a2-4e30-a42a-6d8237a5953d + FUNNEL_ID: 0ff6a9ac-1667-4fc7-9a01-b1620810509f + networks: + - traefik-public + - rspace-internal + - payment-network + labels: + <<: *traefik-enabled + traefik.http.routers.rfunds-sa.rule: Host(`rfunds.online`) + traefik.http.routers.rfunds-sa.entrypoints: web + traefik.http.services.rfunds-sa.loadbalancer.server.port: "3000" + + # ── rFiles ── + rfiles-standalone: + <<: *standalone-base + container_name: rfiles-standalone + command: ["bun", "run", "modules/files/standalone.ts"] + volumes: + - rspace-files:/data/files + environment: + <<: *base-env + FILES_DIR: /data/files + labels: + <<: *traefik-enabled + traefik.http.routers.rfiles-sa.rule: Host(`rfiles.online`) + traefik.http.routers.rfiles-sa.entrypoints: web + traefik.http.services.rfiles-sa.loadbalancer.server.port: "3000" + + # ── rForum ── + rforum-standalone: + <<: *standalone-base + container_name: rforum-standalone + command: ["bun", "run", "modules/forum/standalone.ts"] + environment: + <<: *base-env + HETZNER_API_TOKEN: ${HETZNER_API_TOKEN} + CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN} + CLOUDFLARE_ZONE_ID: ${CLOUDFLARE_ZONE_ID} + labels: + <<: *traefik-enabled + traefik.http.routers.rforum-sa.rule: Host(`rforum.online`) + traefik.http.routers.rforum-sa.entrypoints: web + traefik.http.services.rforum-sa.loadbalancer.server.port: "3000" + + # ── rVote ── + rvote-standalone: + <<: *standalone-base + container_name: rvote-standalone + command: ["bun", "run", "modules/vote/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rvote-sa.rule: Host(`rvote.online`) + traefik.http.routers.rvote-sa.entrypoints: web + traefik.http.services.rvote-sa.loadbalancer.server.port: "3000" + + # ── rNotes ── + rnotes-standalone: + <<: *standalone-base + container_name: rnotes-standalone + command: ["bun", "run", "modules/notes/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rnotes-sa.rule: Host(`rnotes.online`) + traefik.http.routers.rnotes-sa.entrypoints: web + traefik.http.services.rnotes-sa.loadbalancer.server.port: "3000" + + # ── rMaps ── + rmaps-standalone: + <<: *standalone-base + container_name: rmaps-standalone + command: ["bun", "run", "modules/maps/standalone.ts"] + environment: + <<: *base-env + MAPS_SYNC_URL: wss://sync.rmaps.online + labels: + <<: *traefik-enabled + traefik.http.routers.rmaps-sa.rule: Host(`rmaps.online`) + traefik.http.routers.rmaps-sa.entrypoints: web + traefik.http.services.rmaps-sa.loadbalancer.server.port: "3000" + + # ── rWork ── + rwork-standalone: + <<: *standalone-base + container_name: rwork-standalone + command: ["bun", "run", "modules/work/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rwork-sa.rule: Host(`rwork.online`) + traefik.http.routers.rwork-sa.entrypoints: web + traefik.http.services.rwork-sa.loadbalancer.server.port: "3000" + + # ── rTrips ── + rtrips-standalone: + <<: *standalone-base + container_name: rtrips-standalone + command: ["bun", "run", "modules/trips/standalone.ts"] + environment: + <<: *base-env + OSRM_URL: http://osrm-backend:5000 + labels: + <<: *traefik-enabled + traefik.http.routers.rtrips-sa.rule: Host(`rtrips.online`) + traefik.http.routers.rtrips-sa.entrypoints: web + traefik.http.services.rtrips-sa.loadbalancer.server.port: "3000" + + # ── rCal ── + rcal-standalone: + <<: *standalone-base + container_name: rcal-standalone + command: ["bun", "run", "modules/cal/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rcal-sa.rule: Host(`rcal.online`) + traefik.http.routers.rcal-sa.entrypoints: web + traefik.http.services.rcal-sa.loadbalancer.server.port: "3000" + + # ── rNetwork ── + rnetwork-standalone: + <<: *standalone-base + container_name: rnetwork-standalone + command: ["bun", "run", "modules/network/standalone.ts"] + environment: + <<: *base-env + TWENTY_API_URL: https://rnetwork.online + TWENTY_API_TOKEN: ${TWENTY_API_TOKEN} + labels: + <<: *traefik-enabled + traefik.http.routers.rnetwork-sa.rule: Host(`rnetwork.online`) + traefik.http.routers.rnetwork-sa.entrypoints: web + traefik.http.services.rnetwork-sa.loadbalancer.server.port: "3000" + + # ── rTube ── + rtube-standalone: + <<: *standalone-base + container_name: rtube-standalone + command: ["bun", "run", "modules/tube/standalone.ts"] + environment: + <<: *base-env + R2_ENDPOINT: ${R2_ENDPOINT} + R2_BUCKET: ${R2_BUCKET:-rtube-videos} + R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID} + R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY} + R2_PUBLIC_URL: ${R2_PUBLIC_URL} + labels: + <<: *traefik-enabled + traefik.http.routers.rtube-sa.rule: Host(`rtube.online`) + traefik.http.routers.rtube-sa.entrypoints: web + traefik.http.services.rtube-sa.loadbalancer.server.port: "3000" + + # ── rInbox ── + rinbox-standalone: + <<: *standalone-base + container_name: rinbox-standalone + command: ["bun", "run", "modules/inbox/standalone.ts"] + environment: + <<: *base-env + IMAP_HOST: mail.rmail.online + IMAP_PORT: "993" + IMAP_TLS_REJECT_UNAUTHORIZED: "false" + networks: + - traefik-public + - rspace-internal + - rmail-mailcow + labels: + <<: *traefik-enabled + traefik.http.routers.rinbox-sa.rule: Host(`rinbox.online`) + traefik.http.routers.rinbox-sa.entrypoints: web + traefik.http.services.rinbox-sa.loadbalancer.server.port: "3000" + + # ── rData ── + rdata-standalone: + <<: *standalone-base + container_name: rdata-standalone + command: ["bun", "run", "modules/data/standalone.ts"] + environment: + <<: *base-env + UMAMI_URL: https://analytics.rspace.online + UMAMI_WEBSITE_ID: 292f6ac6-79f8-497b-ba6a-7a51e3b87b9f + labels: + <<: *traefik-enabled + traefik.http.routers.rdata-sa.rule: Host(`rdata.online`) + traefik.http.routers.rdata-sa.entrypoints: web + traefik.http.services.rdata-sa.loadbalancer.server.port: "3000" + + # ── rWallet ── + rwallet-standalone: + <<: *standalone-base + container_name: rwallet-standalone + command: ["bun", "run", "modules/wallet/standalone.ts"] + labels: + <<: *traefik-enabled + traefik.http.routers.rwallet-sa.rule: Host(`rwallet.online`) + traefik.http.routers.rwallet-sa.entrypoints: web + traefik.http.services.rwallet-sa.loadbalancer.server.port: "3000" + +# Volumes and networks inherited from main docker-compose.yml +# Use: docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -d