@better-t-stack/template-generator 3.32.0 → 3.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -187,6 +187,7 @@ Handlebars.registerHelper("eq", (a, b) => a === b);
187
187
  Handlebars.registerHelper("ne", (a, b) => a !== b);
188
188
  Handlebars.registerHelper("and", (...args) => args.slice(0, -1).every(Boolean));
189
189
  Handlebars.registerHelper("or", (...args) => args.slice(0, -1).some(Boolean));
190
+ Handlebars.registerHelper("not", (a) => !a);
190
191
  Handlebars.registerHelper("includes", (arr, val) => Array.isArray(arr) && arr.includes(val));
191
192
  function processTemplateString(content, context) {
192
193
  return Handlebars.compile(content)(context);
@@ -199,6 +200,7 @@ function transformFilename(filename) {
199
200
  const basename = result.split("/").pop() || result;
200
201
  if (basename === "_gitignore") result = result.replace(/_gitignore$/, ".gitignore");
201
202
  else if (basename === "_npmrc") result = result.replace(/_npmrc$/, ".npmrc");
203
+ else if (basename === "_dockerignore") result = result.replace(/_dockerignore$/, ".dockerignore");
202
204
  return result;
203
205
  }
204
206
  function processFileContent(filePath, content, context) {
@@ -489,6 +491,7 @@ const dependencyVersionMap = {
489
491
  "@opennextjs/cloudflare": "^1.17.3",
490
492
  "nitro-cloudflare-dev": "^0.2.2",
491
493
  "@sveltejs/adapter-cloudflare": "^7.2.8",
494
+ "@sveltejs/adapter-node": "^5.5.4",
492
495
  "@cloudflare/workers-types": "^4.20251213.0",
493
496
  "@astrojs/cloudflare": "^13.0.1",
494
497
  "@astrojs/node": "^10.0.0-beta.9",
@@ -638,7 +641,13 @@ function updateRootPackageJson(vfs, config) {
638
641
  }
639
642
  }
640
643
  if (database === "sqlite" && dbSetup !== "d1") scripts["db:local"] = pmConfig.filter(dbPackageName, "db:local");
641
- if (dbSetup === "docker") {
644
+ const hasDockerDeployScripts = config.webDeploy === "docker" || config.serverDeploy === "docker";
645
+ if (dbSetup === "docker") if (hasDockerDeployScripts) {
646
+ scripts["db:start"] = `docker compose up -d ${database}`;
647
+ scripts["db:watch"] = `docker compose up ${database}`;
648
+ scripts["db:stop"] = `docker compose stop ${database}`;
649
+ scripts["db:down"] = `docker compose down ${database}`;
650
+ } else {
642
651
  scripts["db:start"] = pmConfig.filter(dbPackageName, "db:start");
643
652
  scripts["db:watch"] = pmConfig.filter(dbPackageName, "db:watch");
644
653
  scripts["db:stop"] = pmConfig.filter(dbPackageName, "db:stop");
@@ -649,6 +658,12 @@ function updateRootPackageJson(vfs, config) {
649
658
  scripts.deploy = pmConfig.filter(infraPackageName, "deploy");
650
659
  scripts.destroy = pmConfig.filter(infraPackageName, "destroy");
651
660
  }
661
+ if (config.webDeploy === "docker" || config.serverDeploy === "docker") {
662
+ scripts["docker:build"] = "docker compose build";
663
+ scripts["docker:up"] = "docker compose up -d --build";
664
+ scripts["docker:down"] = "docker compose down";
665
+ scripts["docker:logs"] = "docker compose logs -f";
666
+ }
652
667
  pkgJson.packageManager = `${packageManager}@latest`;
653
668
  if (config.api === "orpc" && config.frontend.includes("nuxt")) pkgJson.overrides = {
654
669
  ...pkgJson.overrides,
@@ -796,7 +811,8 @@ function updateDbPackageJson(vfs, config) {
796
811
  }
797
812
  }
798
813
  }
799
- if (dbSetup === "docker") {
814
+ const hasDockerDeploy = config.webDeploy === "docker" || config.serverDeploy === "docker";
815
+ if (dbSetup === "docker" && !hasDockerDeploy) {
800
816
  scripts["db:start"] = "docker compose up -d";
801
817
  scripts["db:watch"] = "docker compose up";
802
818
  scripts["db:stop"] = "docker compose stop";
@@ -2021,8 +2037,24 @@ function processDeployDeps(vfs, config) {
2021
2037
  const { webDeploy, serverDeploy, frontend, backend } = config;
2022
2038
  const isCloudflareWeb = webDeploy === "cloudflare";
2023
2039
  const isCloudflareServer = serverDeploy === "cloudflare";
2040
+ const isDockerWeb = webDeploy === "docker";
2024
2041
  const isBackendSelf = backend === "self";
2025
- if (!isCloudflareWeb && !isCloudflareServer) return;
2042
+ if (!isCloudflareWeb && !isCloudflareServer && !isDockerWeb) return;
2043
+ if (isDockerWeb) {
2044
+ const webPkgPath = "apps/web/package.json";
2045
+ if (vfs.exists(webPkgPath)) {
2046
+ if (frontend.includes("svelte")) addPackageDependency({
2047
+ vfs,
2048
+ packagePath: webPkgPath,
2049
+ devDependencies: ["@sveltejs/adapter-node"]
2050
+ });
2051
+ else if (frontend.includes("tanstack-start")) addPackageDependency({
2052
+ vfs,
2053
+ packagePath: webPkgPath,
2054
+ dependencies: ["nitro"]
2055
+ });
2056
+ }
2057
+ }
2026
2058
  if (isCloudflareWeb || isCloudflareServer) addPackageDependency({
2027
2059
  vfs,
2028
2060
  packagePath: "package.json",
@@ -3321,7 +3353,7 @@ ${packageManagerRunCmd} db:push
3321
3353
  return setup;
3322
3354
  }
3323
3355
  function generateScriptsList(packageManagerRunCmd, config, hasNative) {
3324
- const { database, addons, backend, dbSetup, frontend } = config;
3356
+ const { database, addons, backend, dbSetup, frontend, webDeploy, serverDeploy } = config;
3325
3357
  const isConvex = backend === "convex";
3326
3358
  const isBackendSelf = backend === "self";
3327
3359
  const hasWeb = frontend.some((f) => [
@@ -3374,14 +3406,25 @@ function generateScriptsList(packageManagerRunCmd, config, hasNative) {
3374
3406
  }
3375
3407
  if (addons.includes("starlight")) scripts += `\n- \`cd apps/docs && ${packageManagerRunCmd} dev\`: Start documentation site
3376
3408
  - \`cd apps/docs && ${packageManagerRunCmd} build\`: Build documentation site`;
3409
+ if (webDeploy === "docker" || serverDeploy === "docker") scripts += `\n- \`${packageManagerRunCmd} docker:build\`: Build the Docker Compose images
3410
+ - \`${packageManagerRunCmd} docker:up\`: Build and start the Docker Compose stack
3411
+ - \`${packageManagerRunCmd} docker:logs\`: Tail logs from the Docker Compose stack
3412
+ - \`${packageManagerRunCmd} docker:down\`: Stop the Docker Compose stack`;
3377
3413
  return scripts;
3378
3414
  }
3379
3415
  function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy, backend) {
3380
- if (webDeploy !== "cloudflare" && serverDeploy !== "cloudflare") return "";
3381
- const lines = ["## Deployment (Cloudflare via Alchemy)"];
3382
- const targetLabel = webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || backend === "self") ? "web + server" : webDeploy === "cloudflare" ? "web" : "server";
3383
- lines.push(`- Target: ${targetLabel}`, `- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`);
3384
- lines.push("", "For more details, see the guide on [Deploying to Cloudflare with Alchemy](https://www.better-t-stack.dev/docs/guides/cloudflare-alchemy).");
3416
+ const hasCloudflare = webDeploy === "cloudflare" || serverDeploy === "cloudflare";
3417
+ const hasDocker = webDeploy === "docker" || serverDeploy === "docker";
3418
+ if (!hasCloudflare && !hasDocker) return "";
3419
+ const lines = ["## Deployment"];
3420
+ if (hasCloudflare) {
3421
+ const targetLabel = webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || backend === "self") ? "web + server" : webDeploy === "cloudflare" ? "web" : "server";
3422
+ lines.push("", "### Cloudflare via Alchemy", "", `- Target: ${targetLabel}`, `- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`, "", "For more details, see the guide on [Deploying to Cloudflare with Alchemy](https://www.better-t-stack.dev/docs/guides/cloudflare-alchemy).");
3423
+ }
3424
+ if (hasDocker) {
3425
+ const targetLabel = webDeploy === "docker" && (serverDeploy === "docker" || backend === "self") ? "web + server" : webDeploy === "docker" ? "web" : "server";
3426
+ lines.push("", "### Docker Compose", "", `- Target: ${targetLabel}`, "- Config: `docker-compose.yml` (app Dockerfiles live in `apps/*/Dockerfile`)", `- Build images: ${packageManagerRunCmd} docker:build`, `- Start: ${packageManagerRunCmd} docker:up`, `- Logs: ${packageManagerRunCmd} docker:logs`, `- Stop: ${packageManagerRunCmd} docker:down`, "", "Environment variables are read from each app's `.env` file (baked into web builds for public variables) and overridden in `docker-compose.yml` for container networking.");
3427
+ }
3385
3428
  return `${lines.join("\n")}\n`;
3386
3429
  }
3387
3430
  function generateGitHooksSection(packageManagerRunCmd, addons) {
@@ -3831,7 +3874,8 @@ async function processDbTemplates(vfs, templates, config) {
3831
3874
  processTemplatesFromPrefix(vfs, templates, "db/base", "packages/db", config);
3832
3875
  processTemplatesFromPrefix(vfs, templates, `db/${config.orm}/base`, "packages/db", config);
3833
3876
  processTemplatesFromPrefix(vfs, templates, `db/${config.orm}/${config.database}`, "packages/db", config);
3834
- if (config.dbSetup === "docker") processTemplatesFromPrefix(vfs, templates, `db-setup/docker-compose/${config.database}`, "packages/db", config);
3877
+ const hasDockerDeploy = config.webDeploy === "docker" || config.serverDeploy === "docker";
3878
+ if (config.dbSetup === "docker" && !hasDockerDeploy) processTemplatesFromPrefix(vfs, templates, `db-setup/docker-compose/${config.database}`, "packages/db", config);
3835
3879
  }
3836
3880
  //#endregion
3837
3881
  //#region src/template-handlers/api.ts
@@ -4132,6 +4176,7 @@ async function processExtrasTemplates(vfs, templates, config) {
4132
4176
  async function processDeployTemplates(vfs, templates, config) {
4133
4177
  const isBackendSelf = config.backend === "self";
4134
4178
  if (config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") processTemplatesFromPrefix(vfs, templates, "packages/infra", "packages/infra", config);
4179
+ if (config.webDeploy === "docker" || config.serverDeploy === "docker") processTemplatesFromPrefix(vfs, templates, "deploy/docker/compose", "", config);
4135
4180
  if (config.webDeploy !== "none" && config.webDeploy !== "cloudflare") {
4136
4181
  const templateMap = {
4137
4182
  "tanstack-router": "react/tanstack-router",
@@ -4140,7 +4185,8 @@ async function processDeployTemplates(vfs, templates, config) {
4140
4185
  solid: "solid",
4141
4186
  next: "react/next",
4142
4187
  nuxt: "nuxt",
4143
- svelte: "svelte"
4188
+ svelte: "svelte",
4189
+ astro: "astro"
4144
4190
  };
4145
4191
  for (const f of config.frontend) if (templateMap[f]) processTemplatesFromPrefix(vfs, templates, `deploy/${config.webDeploy}/web/${templateMap[f]}`, "apps/web", config);
4146
4192
  }
@@ -5528,7 +5574,9 @@ import { createTanstackQueryUtils } from "@orpc/tanstack-query";
5528
5574
 
5529
5575
  export default defineNuxtPlugin(() => {
5530
5576
  const config = useRuntimeConfig();
5531
- const rpcUrl = \`\${config.public.serverUrl}/rpc\`;
5577
+ const serverUrl =
5578
+ (import.meta.server && config.serverUrl) || config.public.serverUrl;
5579
+ const rpcUrl = \`\${serverUrl}/rpc\`;
5532
5580
 
5533
5581
  const rpcLink = new RPCLink({
5534
5582
  url: rpcUrl,
@@ -13774,7 +13822,7 @@ export default defineNuxtPlugin(() => {
13774
13822
 
13775
13823
  const authClient = createAuthClient({
13776
13824
  {{#if (ne backend "self")}}
13777
- baseURL: config.public.serverUrl,
13825
+ baseURL: (import.meta.server && config.serverUrl) || config.public.serverUrl,
13778
13826
  {{/if}}
13779
13827
  {{#if (eq payments "polar")}}
13780
13828
  plugins: [polarClient()],
@@ -19009,7 +19057,7 @@ fastify.get('/', async () => {
19009
19057
  return 'OK';
19010
19058
  });
19011
19059
 
19012
- fastify.listen({ port: 3000 }, (err) => {
19060
+ fastify.listen({ port: 3000{{#if (eq serverDeploy "docker")}}, host: "0.0.0.0"{{/if}} }, (err) => {
19013
19061
  if (err) {
19014
19062
  fastify.log.error(err);
19015
19063
  process.exit(1);
@@ -20088,6 +20136,710 @@ const prisma = createPrismaClient();
20088
20136
  export default prisma;
20089
20137
  {{/if}}
20090
20138
  {{/if}}
20139
+ `],
20140
+ ["deploy/docker/compose/_dockerignore", `**/node_modules
20141
+ .git
20142
+
20143
+ **/dist
20144
+ **/build
20145
+ **/.next
20146
+ **/.nuxt
20147
+ **/.output
20148
+ **/.svelte-kit
20149
+ **/.astro
20150
+ **/.turbo
20151
+ .turbo
20152
+
20153
+ **/.wrangler
20154
+ **/.alchemy
20155
+ **/.expo
20156
+ **/.vercel
20157
+ *.log
20158
+
20159
+ Dockerfile
20160
+ **/Dockerfile
20161
+ docker-compose.yml
20162
+
20163
+ # Secrets stay out of image layers; runtime env comes from compose env_file,
20164
+ # build-time public values come from compose build args
20165
+ **/.env
20166
+ **/.env.*
20167
+ !**/.env.example
20168
+ `],
20169
+ ["deploy/docker/compose/docker-compose.yml.hbs", `name: {{projectName}}
20170
+
20171
+ services:
20172
+ {{#if (eq webDeploy "docker")}}
20173
+ web:
20174
+ build:
20175
+ context: .
20176
+ dockerfile: apps/web/Dockerfile
20177
+ {{#if (and (not (includes frontend "nuxt")) (or (and (ne backend "self") (ne backend "none") (ne backend "convex")) (eq backend "convex") (and (eq auth "clerk") (or (includes frontend "next") (includes frontend "react-router") (includes frontend "tanstack-router") (includes frontend "tanstack-start")))))}}
20178
+ args:
20179
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20180
+ {{#if (includes frontend "next")}}NEXT_PUBLIC_SERVER_URL{{else if (or (includes frontend "svelte") (includes frontend "astro"))}}PUBLIC_SERVER_URL{{else}}VITE_SERVER_URL{{/if}}: http://localhost:3000
20181
+ {{/if}}
20182
+ {{#if (eq backend "convex")}}
20183
+ {{#if (includes frontend "next")}}NEXT_PUBLIC_CONVEX_URL{{else if (or (includes frontend "svelte") (includes frontend "astro"))}}PUBLIC_CONVEX_URL{{else}}VITE_CONVEX_URL{{/if}}: \${CONVEX_URL:-}
20184
+ {{/if}}
20185
+ {{#if (and (eq auth "clerk") (or (includes frontend "next") (includes frontend "react-router") (includes frontend "tanstack-router") (includes frontend "tanstack-start")))}}
20186
+ {{#if (includes frontend "next")}}NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY{{else}}VITE_CLERK_PUBLISHABLE_KEY{{/if}}: \${CLERK_PUBLISHABLE_KEY:-}
20187
+ {{/if}}
20188
+ {{/if}}
20189
+ init: true
20190
+ ports:
20191
+ {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "solid"))}}
20192
+ - "3001:80"
20193
+ {{else}}
20194
+ - "3001:3001"
20195
+ {{/if}}
20196
+ env_file:
20197
+ - path: apps/web/.env
20198
+ required: false
20199
+ {{#if (eq backend "self")}}
20200
+ {{#if (or (eq dbSetup "docker") (eq auth "better-auth"))}}
20201
+ environment:
20202
+ {{#if (eq auth "better-auth")}}
20203
+ BETTER_AUTH_URL: http://localhost:3001
20204
+ CORS_ORIGIN: http://localhost:3001
20205
+ {{/if}}
20206
+ {{#if (and (eq dbSetup "docker") (eq database "postgres"))}}
20207
+ DATABASE_URL: postgresql://postgres:\${POSTGRES_PASSWORD:-password}@postgres:5432/{{projectName}}
20208
+ {{/if}}
20209
+ {{#if (and (eq dbSetup "docker") (eq database "mysql"))}}
20210
+ DATABASE_URL: mysql://user:\${MYSQL_PASSWORD:-password}@mysql:3306/{{projectName}}
20211
+ {{/if}}
20212
+ {{#if (and (eq dbSetup "docker") (eq database "mongodb"))}}
20213
+ DATABASE_URL: mongodb://root:\${MONGO_PASSWORD:-password}@mongodb:27017/{{projectName}}?authSource=admin
20214
+ {{/if}}
20215
+ {{/if}}
20216
+ {{#if (eq dbSetup "docker")}}
20217
+ depends_on:
20218
+ {{database}}:
20219
+ condition: service_healthy
20220
+ {{/if}}
20221
+ {{else}}
20222
+ {{#if (eq serverDeploy "docker")}}
20223
+ {{#if (or (includes frontend "next") (includes frontend "nuxt"))}}
20224
+ environment:
20225
+ {{#if (includes frontend "next")}}
20226
+ NEXT_PUBLIC_SERVER_URL: http://server:3000
20227
+ {{/if}}
20228
+ {{#if (includes frontend "nuxt")}}
20229
+ NUXT_SERVER_URL: http://server:3000
20230
+ {{/if}}
20231
+ {{/if}}
20232
+ depends_on:
20233
+ server:
20234
+ condition: service_healthy
20235
+ {{/if}}
20236
+ {{/if}}
20237
+ restart: unless-stopped
20238
+
20239
+ {{/if}}
20240
+ {{#if (and (eq serverDeploy "docker") (ne backend "self"))}}
20241
+ server:
20242
+ build:
20243
+ context: .
20244
+ dockerfile: apps/server/Dockerfile
20245
+ init: true
20246
+ ports:
20247
+ - "3000:3000"
20248
+ env_file:
20249
+ - path: apps/server/.env
20250
+ required: false
20251
+ {{#if (or (eq webDeploy "docker") (eq dbSetup "docker"))}}
20252
+ environment:
20253
+ {{#if (eq webDeploy "docker")}}
20254
+ CORS_ORIGIN: http://localhost:3001
20255
+ {{/if}}
20256
+ {{#if (and (eq dbSetup "docker") (eq database "postgres"))}}
20257
+ DATABASE_URL: postgresql://postgres:\${POSTGRES_PASSWORD:-password}@postgres:5432/{{projectName}}
20258
+ {{/if}}
20259
+ {{#if (and (eq dbSetup "docker") (eq database "mysql"))}}
20260
+ DATABASE_URL: mysql://user:\${MYSQL_PASSWORD:-password}@mysql:3306/{{projectName}}
20261
+ {{/if}}
20262
+ {{#if (and (eq dbSetup "docker") (eq database "mongodb"))}}
20263
+ DATABASE_URL: mongodb://root:\${MONGO_PASSWORD:-password}@mongodb:27017/{{projectName}}?authSource=admin
20264
+ {{/if}}
20265
+ {{/if}}
20266
+ healthcheck:
20267
+ test:
20268
+ [
20269
+ "CMD",
20270
+ "node",
20271
+ "-e",
20272
+ "fetch('http://localhost:3000/').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))",
20273
+ ]
20274
+ interval: 10s
20275
+ timeout: 5s
20276
+ retries: 5
20277
+ start_period: 10s
20278
+ {{#if (eq dbSetup "docker")}}
20279
+ depends_on:
20280
+ {{database}}:
20281
+ condition: service_healthy
20282
+ {{/if}}
20283
+ restart: unless-stopped
20284
+
20285
+ {{/if}}
20286
+ {{#if (eq dbSetup "docker")}}
20287
+ {{#if (eq database "postgres")}}
20288
+ postgres:
20289
+ image: postgres
20290
+ container_name: {{projectName}}-postgres
20291
+ environment:
20292
+ POSTGRES_DB: {{projectName}}
20293
+ POSTGRES_USER: postgres
20294
+ POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-password}
20295
+ ports:
20296
+ - "5432:5432"
20297
+ volumes:
20298
+ - {{projectName}}_postgres_data:/var/lib/postgresql
20299
+ healthcheck:
20300
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
20301
+ interval: 10s
20302
+ timeout: 5s
20303
+ retries: 5
20304
+ restart: unless-stopped
20305
+ {{/if}}
20306
+ {{#if (eq database "mysql")}}
20307
+ mysql:
20308
+ image: mysql
20309
+ container_name: {{projectName}}-mysql
20310
+ environment:
20311
+ MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-password}
20312
+ MYSQL_DATABASE: {{projectName}}
20313
+ MYSQL_USER: user
20314
+ MYSQL_PASSWORD: \${MYSQL_PASSWORD:-password}
20315
+ ports:
20316
+ - "3306:3306"
20317
+ volumes:
20318
+ - {{projectName}}_mysql_data:/var/lib/mysql
20319
+ healthcheck:
20320
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
20321
+ interval: 10s
20322
+ timeout: 5s
20323
+ retries: 5
20324
+ restart: unless-stopped
20325
+ {{/if}}
20326
+ {{#if (eq database "mongodb")}}
20327
+ mongodb:
20328
+ image: mongo
20329
+ container_name: {{projectName}}-mongodb
20330
+ environment:
20331
+ MONGO_INITDB_ROOT_USERNAME: root
20332
+ MONGO_INITDB_ROOT_PASSWORD: \${MONGO_PASSWORD:-password}
20333
+ MONGO_INITDB_DATABASE: {{projectName}}
20334
+ ports:
20335
+ - "27017:27017"
20336
+ volumes:
20337
+ - {{projectName}}_mongodb_data:/data/db
20338
+ healthcheck:
20339
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
20340
+ interval: 10s
20341
+ timeout: 5s
20342
+ retries: 5
20343
+ restart: unless-stopped
20344
+ {{/if}}
20345
+
20346
+ volumes:
20347
+ {{projectName}}_{{database}}_data:
20348
+ {{/if}}
20349
+ `],
20350
+ ["deploy/docker/server/Dockerfile.hbs", `FROM node:24-slim AS base
20351
+ {{#if (or (eq packageManager "bun") (eq runtime "bun"))}}
20352
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20353
+ {{/if}}
20354
+ {{#if (eq packageManager "pnpm")}}
20355
+ RUN npm install -g pnpm
20356
+ {{/if}}
20357
+ WORKDIR /app
20358
+ ENV SKIP_ENV_VALIDATION=1
20359
+ {{#if (eq orm "prisma")}}
20360
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20361
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20362
+ {{/if}}
20363
+
20364
+ COPY . .
20365
+ {{#if (eq packageManager "bun")}}
20366
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20367
+ {{else if (eq packageManager "pnpm")}}
20368
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20369
+ {{else}}
20370
+ RUN --mount=type=cache,target=/root/.npm npm install
20371
+ {{/if}}
20372
+
20373
+ ENV NODE_ENV=production
20374
+ RUN cd apps/server && {{packageManager}} run build
20375
+ ENV SKIP_ENV_VALIDATION=
20376
+ {{#if (eq orm "prisma")}}
20377
+ ENV DATABASE_URL=
20378
+ {{/if}}
20379
+
20380
+ EXPOSE 3000
20381
+
20382
+ WORKDIR /app/apps/server
20383
+ {{#if (eq runtime "bun")}}
20384
+ CMD ["bun", "dist/index.mjs"]
20385
+ {{else}}
20386
+ CMD ["node", "dist/index.mjs"]
20387
+ {{/if}}
20388
+ `],
20389
+ ["deploy/docker/web/astro/Dockerfile.hbs", `FROM node:24-slim AS base
20390
+ {{#if (eq packageManager "bun")}}
20391
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20392
+ {{/if}}
20393
+ {{#if (eq packageManager "pnpm")}}
20394
+ RUN npm install -g pnpm
20395
+ {{/if}}
20396
+ WORKDIR /app
20397
+ ENV SKIP_ENV_VALIDATION=1
20398
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20399
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20400
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20401
+ {{/if}}
20402
+ {{#if (eq orm "prisma")}}
20403
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20404
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20405
+ {{/if}}
20406
+
20407
+ COPY . .
20408
+ {{#if (eq packageManager "bun")}}
20409
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20410
+ {{else if (eq packageManager "pnpm")}}
20411
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20412
+ {{else}}
20413
+ RUN --mount=type=cache,target=/root/.npm npm install
20414
+ {{/if}}
20415
+
20416
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20417
+ ARG PUBLIC_SERVER_URL
20418
+ ENV PUBLIC_SERVER_URL=\${PUBLIC_SERVER_URL}
20419
+ {{/if}}
20420
+ {{#if (eq backend "convex")}}
20421
+ ARG PUBLIC_CONVEX_URL
20422
+ ENV PUBLIC_CONVEX_URL=\${PUBLIC_CONVEX_URL}
20423
+ {{/if}}
20424
+ ENV NODE_ENV=production
20425
+ RUN cd apps/web && {{packageManager}} run build
20426
+ ENV SKIP_ENV_VALIDATION=
20427
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20428
+ ENV BETTER_AUTH_SECRET=
20429
+ {{/if}}
20430
+ {{#if (eq orm "prisma")}}
20431
+ ENV DATABASE_URL=
20432
+ {{/if}}
20433
+
20434
+ ENV HOST=0.0.0.0
20435
+ ENV PORT=3001
20436
+ EXPOSE 3001
20437
+
20438
+ WORKDIR /app/apps/web
20439
+ CMD ["node", "dist/server/entry.mjs"]
20440
+ `],
20441
+ ["deploy/docker/web/nuxt/Dockerfile.hbs", `FROM node:24-slim AS builder
20442
+ {{#if (eq packageManager "bun")}}
20443
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20444
+ {{/if}}
20445
+ {{#if (eq packageManager "pnpm")}}
20446
+ RUN npm install -g pnpm
20447
+ {{/if}}
20448
+ WORKDIR /app
20449
+ ENV SKIP_ENV_VALIDATION=1
20450
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20451
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20452
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20453
+ {{/if}}
20454
+ {{#if (eq orm "prisma")}}
20455
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20456
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20457
+ {{/if}}
20458
+
20459
+ COPY . .
20460
+ {{#if (eq packageManager "bun")}}
20461
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20462
+ {{else if (eq packageManager "pnpm")}}
20463
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20464
+ {{else}}
20465
+ RUN --mount=type=cache,target=/root/.npm npm install
20466
+ {{/if}}
20467
+
20468
+ ENV NODE_ENV=production
20469
+ RUN cd apps/web && {{packageManager}} run build
20470
+
20471
+ FROM node:24-slim AS runner
20472
+ WORKDIR /app
20473
+ ENV NODE_ENV=production
20474
+
20475
+ COPY --from=builder /app/apps/web/.output ./
20476
+
20477
+ ENV HOST=0.0.0.0
20478
+ ENV PORT=3001
20479
+ EXPOSE 3001
20480
+
20481
+ CMD ["node", "server/index.mjs"]
20482
+ `],
20483
+ ["deploy/docker/web/react/next/Dockerfile.hbs", `FROM node:24-slim AS builder
20484
+ {{#if (eq packageManager "bun")}}
20485
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20486
+ {{/if}}
20487
+ {{#if (eq packageManager "pnpm")}}
20488
+ RUN npm install -g pnpm
20489
+ {{/if}}
20490
+ WORKDIR /app
20491
+ ENV SKIP_ENV_VALIDATION=1
20492
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20493
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20494
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20495
+ {{/if}}
20496
+ {{#if (eq orm "prisma")}}
20497
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20498
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20499
+ {{/if}}
20500
+
20501
+ COPY . .
20502
+ {{#if (eq packageManager "bun")}}
20503
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20504
+ {{else if (eq packageManager "pnpm")}}
20505
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20506
+ {{else}}
20507
+ RUN --mount=type=cache,target=/root/.npm npm install
20508
+ {{/if}}
20509
+
20510
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20511
+ ARG NEXT_PUBLIC_SERVER_URL
20512
+ ENV NEXT_PUBLIC_SERVER_URL=\${NEXT_PUBLIC_SERVER_URL}
20513
+ {{/if}}
20514
+ {{#if (eq backend "convex")}}
20515
+ ARG NEXT_PUBLIC_CONVEX_URL
20516
+ ENV NEXT_PUBLIC_CONVEX_URL=\${NEXT_PUBLIC_CONVEX_URL}
20517
+ {{/if}}
20518
+ {{#if (eq auth "clerk")}}
20519
+ ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
20520
+ ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=\${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
20521
+ {{/if}}
20522
+ ENV NODE_ENV=production
20523
+ RUN cd apps/web && {{packageManager}} run build
20524
+
20525
+ FROM node:24-slim AS runner
20526
+ WORKDIR /app
20527
+ ENV NODE_ENV=production
20528
+
20529
+ COPY --from=builder /app/apps/web/.next/standalone ./
20530
+ COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
20531
+
20532
+ ENV HOSTNAME=0.0.0.0
20533
+ ENV PORT=3001
20534
+ EXPOSE 3001
20535
+
20536
+ CMD ["node", "apps/web/server.js"]
20537
+ `],
20538
+ ["deploy/docker/web/react/react-router/Dockerfile.hbs", `FROM node:24-slim AS builder
20539
+ {{#if (eq packageManager "bun")}}
20540
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20541
+ {{/if}}
20542
+ {{#if (eq packageManager "pnpm")}}
20543
+ RUN npm install -g pnpm
20544
+ {{/if}}
20545
+ WORKDIR /app
20546
+ ENV SKIP_ENV_VALIDATION=1
20547
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20548
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20549
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20550
+ {{/if}}
20551
+ {{#if (eq orm "prisma")}}
20552
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20553
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20554
+ {{/if}}
20555
+
20556
+ COPY . .
20557
+ {{#if (eq packageManager "bun")}}
20558
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20559
+ {{else if (eq packageManager "pnpm")}}
20560
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20561
+ {{else}}
20562
+ RUN --mount=type=cache,target=/root/.npm npm install
20563
+ {{/if}}
20564
+
20565
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20566
+ ARG VITE_SERVER_URL
20567
+ ENV VITE_SERVER_URL=\${VITE_SERVER_URL}
20568
+ {{/if}}
20569
+ {{#if (eq backend "convex")}}
20570
+ ARG VITE_CONVEX_URL
20571
+ ENV VITE_CONVEX_URL=\${VITE_CONVEX_URL}
20572
+ {{/if}}
20573
+ {{#if (eq auth "clerk")}}
20574
+ ARG VITE_CLERK_PUBLISHABLE_KEY
20575
+ ENV VITE_CLERK_PUBLISHABLE_KEY=\${VITE_CLERK_PUBLISHABLE_KEY}
20576
+ {{/if}}
20577
+ ENV NODE_ENV=production
20578
+ RUN cd apps/web && {{packageManager}} run build
20579
+
20580
+ FROM nginx:alpine AS runner
20581
+ COPY --from=builder /app/apps/web/build/client /usr/share/nginx/html
20582
+ COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
20583
+ EXPOSE 80
20584
+ CMD ["nginx", "-g", "daemon off;"]
20585
+ `],
20586
+ ["deploy/docker/web/react/react-router/nginx.conf", `server {
20587
+ listen 80;
20588
+ server_name _;
20589
+ root /usr/share/nginx/html;
20590
+ index index.html;
20591
+
20592
+ gzip on;
20593
+ gzip_types text/plain text/css application/json application/javascript image/svg+xml;
20594
+
20595
+ location /assets/ {
20596
+ add_header Cache-Control "public, max-age=31536000, immutable";
20597
+ }
20598
+
20599
+ # SPA fallback
20600
+ location / {
20601
+ try_files $uri $uri/ /index.html;
20602
+ }
20603
+ }
20604
+ `],
20605
+ ["deploy/docker/web/react/tanstack-router/Dockerfile.hbs", `FROM node:24-slim AS builder
20606
+ {{#if (eq packageManager "bun")}}
20607
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20608
+ {{/if}}
20609
+ {{#if (eq packageManager "pnpm")}}
20610
+ RUN npm install -g pnpm
20611
+ {{/if}}
20612
+ WORKDIR /app
20613
+ ENV SKIP_ENV_VALIDATION=1
20614
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20615
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20616
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20617
+ {{/if}}
20618
+ {{#if (eq orm "prisma")}}
20619
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20620
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20621
+ {{/if}}
20622
+
20623
+ COPY . .
20624
+ {{#if (eq packageManager "bun")}}
20625
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20626
+ {{else if (eq packageManager "pnpm")}}
20627
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20628
+ {{else}}
20629
+ RUN --mount=type=cache,target=/root/.npm npm install
20630
+ {{/if}}
20631
+
20632
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20633
+ ARG VITE_SERVER_URL
20634
+ ENV VITE_SERVER_URL=\${VITE_SERVER_URL}
20635
+ {{/if}}
20636
+ {{#if (eq backend "convex")}}
20637
+ ARG VITE_CONVEX_URL
20638
+ ENV VITE_CONVEX_URL=\${VITE_CONVEX_URL}
20639
+ {{/if}}
20640
+ {{#if (eq auth "clerk")}}
20641
+ ARG VITE_CLERK_PUBLISHABLE_KEY
20642
+ ENV VITE_CLERK_PUBLISHABLE_KEY=\${VITE_CLERK_PUBLISHABLE_KEY}
20643
+ {{/if}}
20644
+ ENV NODE_ENV=production
20645
+ RUN cd apps/web && {{packageManager}} run build
20646
+
20647
+ FROM nginx:alpine AS runner
20648
+ COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
20649
+ COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
20650
+ EXPOSE 80
20651
+ CMD ["nginx", "-g", "daemon off;"]
20652
+ `],
20653
+ ["deploy/docker/web/react/tanstack-router/nginx.conf", `server {
20654
+ listen 80;
20655
+ server_name _;
20656
+ root /usr/share/nginx/html;
20657
+ index index.html;
20658
+
20659
+ gzip on;
20660
+ gzip_types text/plain text/css application/json application/javascript image/svg+xml;
20661
+
20662
+ location /assets/ {
20663
+ add_header Cache-Control "public, max-age=31536000, immutable";
20664
+ }
20665
+
20666
+ # SPA fallback
20667
+ location / {
20668
+ try_files $uri $uri/ /index.html;
20669
+ }
20670
+ }
20671
+ `],
20672
+ ["deploy/docker/web/react/tanstack-start/Dockerfile.hbs", `FROM node:24-slim AS base
20673
+ {{#if (eq packageManager "bun")}}
20674
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20675
+ {{/if}}
20676
+ {{#if (eq packageManager "pnpm")}}
20677
+ RUN npm install -g pnpm
20678
+ {{/if}}
20679
+ WORKDIR /app
20680
+ ENV SKIP_ENV_VALIDATION=1
20681
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20682
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20683
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20684
+ {{/if}}
20685
+ {{#if (eq orm "prisma")}}
20686
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20687
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20688
+ {{/if}}
20689
+
20690
+ COPY . .
20691
+ {{#if (eq packageManager "bun")}}
20692
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20693
+ {{else if (eq packageManager "pnpm")}}
20694
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20695
+ {{else}}
20696
+ RUN --mount=type=cache,target=/root/.npm npm install
20697
+ {{/if}}
20698
+
20699
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20700
+ ARG VITE_SERVER_URL
20701
+ ENV VITE_SERVER_URL=\${VITE_SERVER_URL}
20702
+ {{/if}}
20703
+ {{#if (eq backend "convex")}}
20704
+ ARG VITE_CONVEX_URL
20705
+ ENV VITE_CONVEX_URL=\${VITE_CONVEX_URL}
20706
+ {{/if}}
20707
+ {{#if (eq auth "clerk")}}
20708
+ ARG VITE_CLERK_PUBLISHABLE_KEY
20709
+ ENV VITE_CLERK_PUBLISHABLE_KEY=\${VITE_CLERK_PUBLISHABLE_KEY}
20710
+ {{/if}}
20711
+ ENV NODE_ENV=production
20712
+ RUN cd apps/web && {{packageManager}} run build
20713
+ ENV SKIP_ENV_VALIDATION=
20714
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20715
+ ENV BETTER_AUTH_SECRET=
20716
+ {{/if}}
20717
+ {{#if (eq orm "prisma")}}
20718
+ ENV DATABASE_URL=
20719
+ {{/if}}
20720
+
20721
+ ENV HOST=0.0.0.0
20722
+ ENV PORT=3001
20723
+ EXPOSE 3001
20724
+
20725
+ # SSR chunks require() externals at runtime; must run from the workspace
20726
+ WORKDIR /app/apps/web
20727
+ CMD ["node", ".output/server/index.mjs"]
20728
+ `],
20729
+ ["deploy/docker/web/solid/Dockerfile.hbs", `FROM node:24-slim AS builder
20730
+ {{#if (eq packageManager "bun")}}
20731
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20732
+ {{/if}}
20733
+ {{#if (eq packageManager "pnpm")}}
20734
+ RUN npm install -g pnpm
20735
+ {{/if}}
20736
+ WORKDIR /app
20737
+ ENV SKIP_ENV_VALIDATION=1
20738
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20739
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20740
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20741
+ {{/if}}
20742
+ {{#if (eq orm "prisma")}}
20743
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20744
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20745
+ {{/if}}
20746
+
20747
+ COPY . .
20748
+ {{#if (eq packageManager "bun")}}
20749
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20750
+ {{else if (eq packageManager "pnpm")}}
20751
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20752
+ {{else}}
20753
+ RUN --mount=type=cache,target=/root/.npm npm install
20754
+ {{/if}}
20755
+
20756
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20757
+ ARG VITE_SERVER_URL
20758
+ ENV VITE_SERVER_URL=\${VITE_SERVER_URL}
20759
+ {{/if}}
20760
+ {{#if (eq backend "convex")}}
20761
+ ARG VITE_CONVEX_URL
20762
+ ENV VITE_CONVEX_URL=\${VITE_CONVEX_URL}
20763
+ {{/if}}
20764
+ ENV NODE_ENV=production
20765
+ RUN cd apps/web && {{packageManager}} run build
20766
+
20767
+ FROM nginx:alpine AS runner
20768
+ COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
20769
+ COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
20770
+ EXPOSE 80
20771
+ CMD ["nginx", "-g", "daemon off;"]
20772
+ `],
20773
+ ["deploy/docker/web/solid/nginx.conf", `server {
20774
+ listen 80;
20775
+ server_name _;
20776
+ root /usr/share/nginx/html;
20777
+ index index.html;
20778
+
20779
+ gzip on;
20780
+ gzip_types text/plain text/css application/json application/javascript image/svg+xml;
20781
+
20782
+ location /assets/ {
20783
+ add_header Cache-Control "public, max-age=31536000, immutable";
20784
+ }
20785
+
20786
+ # SPA fallback
20787
+ location / {
20788
+ try_files $uri $uri/ /index.html;
20789
+ }
20790
+ }
20791
+ `],
20792
+ ["deploy/docker/web/svelte/Dockerfile.hbs", `FROM node:24-slim AS base
20793
+ {{#if (eq packageManager "bun")}}
20794
+ COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
20795
+ {{/if}}
20796
+ {{#if (eq packageManager "pnpm")}}
20797
+ RUN npm install -g pnpm
20798
+ {{/if}}
20799
+ WORKDIR /app
20800
+ ENV SKIP_ENV_VALIDATION=1
20801
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20802
+ # the build evaluates the auth config; the real secret comes from compose at runtime
20803
+ ENV BETTER_AUTH_SECRET=build-time-placeholder-secret-not-used-at-runtime
20804
+ {{/if}}
20805
+ {{#if (eq orm "prisma")}}
20806
+ # prisma generate resolves DATABASE_URL at install time; the real value comes from compose at runtime
20807
+ ENV DATABASE_URL={{#if (eq database "mysql")}}mysql://build:build@localhost:3306/build{{else if (eq database "mongodb")}}mongodb://localhost:27017/build{{else if (eq database "sqlite")}}file:./build.db{{else}}postgresql://build:build@localhost:5432/build{{/if}}
20808
+ {{/if}}
20809
+
20810
+ COPY . .
20811
+ {{#if (eq packageManager "bun")}}
20812
+ RUN --mount=type=cache,target=/root/.bun/install/cache bun install
20813
+ {{else if (eq packageManager "pnpm")}}
20814
+ RUN --mount=type=cache,target=/pnpm-store pnpm install --store-dir /pnpm-store
20815
+ {{else}}
20816
+ RUN --mount=type=cache,target=/root/.npm npm install
20817
+ {{/if}}
20818
+
20819
+ {{#if (and (ne backend "self") (ne backend "none") (ne backend "convex"))}}
20820
+ ARG PUBLIC_SERVER_URL
20821
+ ENV PUBLIC_SERVER_URL=\${PUBLIC_SERVER_URL}
20822
+ {{/if}}
20823
+ {{#if (eq backend "convex")}}
20824
+ ARG PUBLIC_CONVEX_URL
20825
+ ENV PUBLIC_CONVEX_URL=\${PUBLIC_CONVEX_URL}
20826
+ {{/if}}
20827
+ ENV NODE_ENV=production
20828
+ RUN cd apps/web && {{packageManager}} run build
20829
+ ENV SKIP_ENV_VALIDATION=
20830
+ {{#if (and (eq backend "self") (eq auth "better-auth"))}}
20831
+ ENV BETTER_AUTH_SECRET=
20832
+ {{/if}}
20833
+ {{#if (eq orm "prisma")}}
20834
+ ENV DATABASE_URL=
20835
+ {{/if}}
20836
+
20837
+ ENV HOST=0.0.0.0
20838
+ ENV PORT=3001
20839
+ EXPOSE 3001
20840
+
20841
+ WORKDIR /app/apps/web
20842
+ CMD ["node", "build/index.js"]
20091
20843
  `],
20092
20844
  ["examples/ai/convex/packages/backend/convex/agent.ts.hbs", `import { Agent } from "@convex-dev/agent";
20093
20845
  import { google } from "@ai-sdk/google";
@@ -26620,12 +27372,12 @@ declare module "cloudflare:workers" {
26620
27372
  ["extras/pnpm-workspace.yaml.hbs", `packages:
26621
27373
  - "apps/*"
26622
27374
  - "packages/*"
26623
- {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
27375
+ {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq serverDeploy "docker") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "nuxt") (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
26624
27376
 
26625
27377
  # pnpm 11 blocks dependency lifecycle scripts unless they are approved here.
26626
27378
  # Entries are scoped to packages this generated stack can pull in.
26627
27379
  allowBuilds:
26628
- {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes frontend "tanstack-start"))}}
27380
+ {{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (eq serverDeploy "docker") (includes frontend "tanstack-start"))}}
26629
27381
  esbuild: true
26630
27382
  {{/if}}
26631
27383
  {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next"))}}
@@ -26634,7 +27386,11 @@ allowBuilds:
26634
27386
  {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
26635
27387
  msgpackr-extract: true
26636
27388
  {{/if}}
26637
- {{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes addons "pwa"))}}
27389
+ {{#if (includes frontend "nuxt")}}
27390
+ "@parcel/watcher": true
27391
+ vue-demi: true
27392
+ {{/if}}
27393
+ {{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq webDeploy "docker") (includes addons "pwa"))}}
26638
27394
  sharp: true
26639
27395
  {{/if}}
26640
27396
  {{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare"))}}
@@ -30818,6 +31574,8 @@ export default defineNuxtConfig({
30818
31574
  },
30819
31575
  {{else if (and (ne backend "self") (ne backend "none"))}}
30820
31576
  runtimeConfig: {
31577
+ // server-side override for SSR fetches (NUXT_SERVER_URL); falls back to the public URL
31578
+ serverUrl: "",
30821
31579
  public: {
30822
31580
  serverUrl: process.env.NUXT_PUBLIC_SERVER_URL ?? "",
30823
31581
  }
@@ -30838,7 +31596,8 @@ export default defineNuxtConfig({
30838
31596
  },
30839
31597
  "dependencies": {
30840
31598
  "@nuxt/ui": "^4.5.1",
30841
- "nuxt": "^4.4.4"
31599
+ "nuxt": "^4.4.4",
31600
+ "vue": "^3.5.38"
30842
31601
  },
30843
31602
  "devDependencies": {
30844
31603
  "tailwindcss": "^4.2.1",
@@ -30888,6 +31647,9 @@ import type { NextConfig } from "next";
30888
31647
  const nextConfig: NextConfig = {
30889
31648
  typedRoutes: true,
30890
31649
  reactCompiler: true,
31650
+ {{#if (eq webDeploy "docker")}}
31651
+ output: "standalone",
31652
+ {{/if}}
30891
31653
  {{#if (includes examples "ai")}}
30892
31654
  transpilePackages: ["shiki"],
30893
31655
  {{/if}}
@@ -32858,6 +33620,9 @@ function HomeComponent() {
32858
33620
  `],
32859
33621
  ["frontend/react/tanstack-start/vite.config.ts.hbs", `import { defineConfig } from "{{#if (includes addons "vite-plus")}}vite-plus{{else}}vite{{/if}}";
32860
33622
  import { tanstackStart } from "@tanstack/react-start/plugin/vite";
33623
+ {{#if (eq webDeploy "docker")}}
33624
+ import { nitro } from "nitro/vite";
33625
+ {{/if}}
32861
33626
  import tailwindcss from "@tailwindcss/vite";
32862
33627
  import viteReact from "@vitejs/plugin-react";
32863
33628
 
@@ -32871,6 +33636,9 @@ export default defineConfig({
32871
33636
  plugins: [
32872
33637
  tailwindcss(),
32873
33638
  tanstackStart(),
33639
+ {{#if (eq webDeploy "docker")}}
33640
+ nitro(),
33641
+ {{/if}}
32874
33642
  viteReact(),
32875
33643
  ],
32876
33644
  {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
@@ -33104,6 +33872,7 @@ dist-ssr
33104
33872
  "tailwindcss": "^4.2.2"
33105
33873
  },
33106
33874
  "devDependencies": {
33875
+ "@tanstack/solid-router-devtools": "^1.166.13",
33107
33876
  "vite": "^8.0.8",
33108
33877
  "vite-plugin-solid": "^2.11.12"
33109
33878
  }
@@ -33660,6 +34429,8 @@ const TITLE_TEXT = \`
33660
34429
  ["frontend/svelte/static/favicon.png", `[Binary file]`],
33661
34430
  ["frontend/svelte/svelte.config.js.hbs", `{{#if (eq webDeploy "cloudflare")}}
33662
34431
  import alchemy from 'alchemy/cloudflare/sveltekit';
34432
+ {{else if (eq webDeploy "docker")}}
34433
+ import adapter from '@sveltejs/adapter-node';
33663
34434
  {{else}}
33664
34435
  import adapter from '@sveltejs/adapter-auto';
33665
34436
  {{/if}}
@@ -33675,6 +34446,9 @@ const config = {
33675
34446
  {{#if (eq webDeploy "cloudflare")}}
33676
34447
  // Alchemy's adapter wraps SvelteKit's Cloudflare adapter for local platform.env and Worker builds.
33677
34448
  adapter: alchemy()
34449
+ {{else if (eq webDeploy "docker")}}
34450
+ // adapter-node builds a standalone Node server (run with \`node build/index.js\`).
34451
+ adapter: adapter()
33678
34452
  {{else}}
33679
34453
  // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
33680
34454
  // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
@@ -33930,6 +34704,7 @@ export const env = createEnv({
33930
34704
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
33931
34705
  },
33932
34706
  runtimeEnv: process.env,
34707
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
33933
34708
  emptyStringAsUndefined: true,
33934
34709
  });
33935
34710
  {{/if}}
@@ -34055,6 +34830,7 @@ export const env = createEnv({
34055
34830
  runtimeEnv: (import.meta as any).env,
34056
34831
  {{/if}}
34057
34832
  {{/if}}
34833
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
34058
34834
  emptyStringAsUndefined: true,
34059
34835
  });
34060
34836
  `],
@@ -35511,7 +36287,7 @@ function SuccessPage() {
35511
36287
  </div>
35512
36288
  `]
35513
36289
  ]);
35514
- const TEMPLATE_COUNT = 483;
36290
+ const TEMPLATE_COUNT = 497;
35515
36291
  //#endregion
35516
36292
  export { EMBEDDED_TEMPLATES, GeneratorError, Handlebars, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generate, generateReproducibleCommand, isBinaryFile, processAddonTemplates, processAddonsDeps, processFileContent, processNxConfig, processPackageConfigs, processTemplateString, processTurboConfig, processVitePlusConfig, transformFilename, writeBtsConfigToVfs };
35517
36293