From f7965cb22454242aaa1c8ff792855ebe28811901 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 11:10:30 +0530 Subject: [PATCH 01/11] build: drop external dockerfile frontend directive Railway's Buildkit rejects `# syntax=docker.io/docker/dockerfile:1`. Default frontend works fine for this multi-stage build. --- apps/web/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 95c28bce770..7f76660ffa6 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,5 +1,3 @@ -# syntax=docker.io/docker/dockerfile:1 - FROM node:24-alpine AS base RUN corepack enable From 308f1113da2e0373b6dd4868028203fc49c3e9b8 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 11:12:27 +0530 Subject: [PATCH 02/11] build(media-server): use standalone Dockerfile from sub-dir context Railway sub-directory deploys (rootDirectory=apps/media-server) need the standalone Dockerfile whose COPY paths are relative to apps/media-server, not the root Dockerfile that expects repo-root paths. --- apps/media-server/railway.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/media-server/railway.toml b/apps/media-server/railway.toml index 559178f4da0..a853244bb9f 100644 --- a/apps/media-server/railway.toml +++ b/apps/media-server/railway.toml @@ -1,5 +1,5 @@ [build] -dockerfilePath = "apps/media-server/Dockerfile" +dockerfilePath = "Dockerfile.standalone" [deploy] startCommand = "bun run src/index.ts" From a6dc7c4c8f2e4dab4934de9921466ac18ebe4f41 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 11:13:23 +0530 Subject: [PATCH 03/11] build: drop pnpm cache mount Railway's Railpack builder rejects `--mount=type=cache,id=pnpm,...` without a cacheKey prefix on the id. Dropping the cache mount; build is slower but correct. --- apps/web/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 7f76660ffa6..bfc15f92f15 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /app COPY . . RUN corepack enable pnpm -RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm i --frozen-lockfile +RUN pnpm i --frozen-lockfile ARG NEXT_PUBLIC_DOCKER_BUILD=true ENV NEXT_PUBLIC_WEB_URL=http://localhost:3000 From c87c556901f58ac7bfbcf49e8c3aa8a1dd902484 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 11:38:35 +0530 Subject: [PATCH 04/11] build(web-cluster): drop pnpm cache mount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same Railway Railpack constraint as apps/web/Dockerfile — id=pnpm without cacheKey prefix is rejected. --- apps/web-cluster/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web-cluster/Dockerfile b/apps/web-cluster/Dockerfile index 41e3881d721..fe55805151d 100644 --- a/apps/web-cluster/Dockerfile +++ b/apps/web-cluster/Dockerfile @@ -8,7 +8,7 @@ COPY . . RUN corepack enable pnpm RUN echo "inject-workspace-packages=true" >> .npmrc -RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm i +RUN pnpm i RUN pnpm run --filter=@cap/web-cluster build RUN pnpm deploy --filter=@cap/web-cluster out From a472acfb89fe364b61e83a3c8d81624504b9eef7 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 11:55:13 +0530 Subject: [PATCH 05/11] build(web-cluster): drop ENTRYPOINT, let Railway startCommand be the full command Railway's startCommand exec's the literal string and does NOT append to a Docker ENTRYPOINT. With `ENTRYPOINT ["deno", "run", "--allow-all"]` Railway tries to run the startCommand as a file path, getting "permission denied". Removing ENTRYPOINT means startCommand becomes the CMD via shell, so `deno run --allow-all src/runner/index.ts` works as expected. --- apps/web-cluster/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web-cluster/Dockerfile b/apps/web-cluster/Dockerfile index fe55805151d..dfced05815f 100644 --- a/apps/web-cluster/Dockerfile +++ b/apps/web-cluster/Dockerfile @@ -21,8 +21,8 @@ COPY --from=builder --chown=deno:deno /app/out /app USER deno -ENTRYPOINT ["deno", "run", "--allow-all"] - EXPOSE 8080 EXPOSE 42069 EXPOSE 42169 + +CMD ["deno", "run", "--allow-all", "src/runner/index.ts"] From ee17754dc7a740f91b3b93aaa3c61b5b5c860829 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 12:06:14 +0530 Subject: [PATCH 06/11] build(web-cluster): env-var driven CMD for shard-manager vs runner Railway's startCommand fights with multi-word commands (treats whole string as executable path). Switching strategy: hardcode CMD in Dockerfile to invoke deno + ${CLUSTER_ENTRY}. Each service sets CLUSTER_ENTRY env var (default = runner) so no startCommand override needed. --- apps/web-cluster/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web-cluster/Dockerfile b/apps/web-cluster/Dockerfile index dfced05815f..b08a344be72 100644 --- a/apps/web-cluster/Dockerfile +++ b/apps/web-cluster/Dockerfile @@ -25,4 +25,5 @@ EXPOSE 8080 EXPOSE 42069 EXPOSE 42169 -CMD ["deno", "run", "--allow-all", "src/runner/index.ts"] +ENV CLUSTER_ENTRY=src/runner/index.ts +CMD sh -c "deno run --allow-all $CLUSTER_ENTRY" From a401e13fd0aef104ced84ed7d442461f30c05969 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 12:16:55 +0530 Subject: [PATCH 07/11] build(web-cluster): split into runner Dockerfile (this) + shard-manager Dockerfile Railway's startCommand override mechanism keeps fighting multi-word commands on this Deno-based image. Hardcoding the CMD per Dockerfile sidesteps the fight entirely. This Dockerfile is now Runner-only; ShardManager gets its own Dockerfile.shard-manager. --- apps/web-cluster/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web-cluster/Dockerfile b/apps/web-cluster/Dockerfile index b08a344be72..dfced05815f 100644 --- a/apps/web-cluster/Dockerfile +++ b/apps/web-cluster/Dockerfile @@ -25,5 +25,4 @@ EXPOSE 8080 EXPOSE 42069 EXPOSE 42169 -ENV CLUSTER_ENTRY=src/runner/index.ts -CMD sh -c "deno run --allow-all $CLUSTER_ENTRY" +CMD ["deno", "run", "--allow-all", "src/runner/index.ts"] From 6cda58763267080fe94750cd04ba8fc314ebba88 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 12:17:12 +0530 Subject: [PATCH 08/11] build(web-cluster): add Dockerfile.shard-manager with hardcoded CMD Variant of apps/web-cluster/Dockerfile whose only difference is the final CMD points at src/shard-manager.ts instead of src/runner/index.ts. --- apps/web-cluster/Dockerfile.shard-manager | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 apps/web-cluster/Dockerfile.shard-manager diff --git a/apps/web-cluster/Dockerfile.shard-manager b/apps/web-cluster/Dockerfile.shard-manager new file mode 100644 index 00000000000..27232ad5534 --- /dev/null +++ b/apps/web-cluster/Dockerfile.shard-manager @@ -0,0 +1,27 @@ +FROM node:24-slim AS base +RUN corepack enable + +FROM base AS builder +WORKDIR /app +COPY . . + +RUN corepack enable pnpm + +RUN echo "inject-workspace-packages=true" >> .npmrc +RUN pnpm i + +RUN pnpm run --filter=@cap/web-cluster build +RUN pnpm deploy --filter=@cap/web-cluster out +RUN cd out && node scripts/post-deploy.ts + +FROM denoland/deno:2.5.3 AS runner +WORKDIR /app + +COPY --from=builder --chown=deno:deno /app/out /app + +USER deno + +EXPOSE 8080 +EXPOSE 42069 + +CMD ["deno", "run", "--allow-all", "src/shard-manager.ts"] From 40343f2b032e0232f06b357cad0cf1fa4cdc0d79 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 12:27:14 +0530 Subject: [PATCH 09/11] fix(utils): publishConfig.exports points to dist for deployed builds post-deploy.ts merges publishConfig into the deployed package.json. Without publishConfig.exports, the deployed package keeps `exports: "./src/index.ts"` and Deno tries to load TS source from node_modules, hitting ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING and crashing the runner. Mirroring the pattern @cap/database uses. --- packages/utils/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/utils/package.json b/packages/utils/package.json index 82f842d4c57..06a3c3a80f6 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -9,7 +9,10 @@ "build": "tsdown" }, "publishConfig": { - "main": "./dist/index.js" + "main": "./dist/index.js", + "exports": { + ".": "./dist/index.js" + } }, "devDependencies": { "react": "^19.1.1", From 4333fec68abd6bc97dd562944fc8951f6d5439d7 Mon Sep 17 00:00:00 2001 From: Mudit Lal <46276282+Mudit-Lal@users.noreply.github.com> Date: Wed, 6 May 2026 14:09:58 +0530 Subject: [PATCH 10/11] fix(shard-manager): bind to 0.0.0.0 instead of localhost Effect Cluster's NodeClusterShardManagerSocket layerSocketServer binds to config.shardManagerAddress (default localhost:8080). With Cap upstream's SST infra they set SHARD_MANAGER_HOST=0.0.0.0 env var to override, but that mapping isn't reliably resolving on Railway. Hardcoding the bind address in shardingConfig works regardless of env-var conventions. --- apps/web-cluster/src/shard-manager.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/web-cluster/src/shard-manager.ts b/apps/web-cluster/src/shard-manager.ts index d1ddf618096..193d88bf3cf 100644 --- a/apps/web-cluster/src/shard-manager.ts +++ b/apps/web-cluster/src/shard-manager.ts @@ -1,3 +1,4 @@ +import { RunnerAddress } from "@effect/cluster"; import { NodeClusterShardManagerSocket, NodeRuntime, @@ -8,6 +9,12 @@ import { DatabaseLive, ShardDatabaseLive } from "./shared/database.ts"; NodeClusterShardManagerSocket.layer({ storage: "sql", + shardingConfig: { + shardManagerAddress: RunnerAddress.make({ + host: "0.0.0.0", + port: 8080, + }), + }, }).pipe( Layer.provide(ShardDatabaseLive), Layer.provide(DatabaseLive), From 6e2f2cdb3b36b3f0791fcdf834bc6ac3ad4a3aa6 Mon Sep 17 00:00:00 2001 From: "railway-app[bot]" <68434857+railway-app[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 08:44:32 +0000 Subject: [PATCH 11/11] fix: pass RunnerAddress as string in shard-manager.ts --- apps/web-cluster/src/shard-manager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web-cluster/src/shard-manager.ts b/apps/web-cluster/src/shard-manager.ts index 193d88bf3cf..bc578f6bb97 100644 --- a/apps/web-cluster/src/shard-manager.ts +++ b/apps/web-cluster/src/shard-manager.ts @@ -7,13 +7,13 @@ import { Layer, Logger } from "effect"; import { DatabaseLive, ShardDatabaseLive } from "./shared/database.ts"; +const shardManagerHost = Deno.env.get("SHARD_MANAGER_HOST") ?? "0.0.0.0"; +const shardManagerAddress = `${shardManagerHost}:8080`; + NodeClusterShardManagerSocket.layer({ storage: "sql", shardingConfig: { - shardManagerAddress: RunnerAddress.make({ - host: "0.0.0.0", - port: 8080, - }), + shardManagerAddress: RunnerAddress.make(shardManagerAddress), }, }).pipe( Layer.provide(ShardDatabaseLive),