-
-
Notifications
You must be signed in to change notification settings - Fork 262
feat(cli): add docker-compose addon for containerized deployments #858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Updated config validation to include backend and runtime parameters for addons. - Enhanced addon tests for docker-compose compatibility with various frontends and backends. - Implemented docker-compose file generation with appropriate Dockerfiles and .dockerignore files for server and web applications. - Added support for PostgreSQL, MySQL, and MongoDB databases in docker-compose configurations. - Introduced new templates for Dockerfiles and nginx configuration tailored for different setups. - Updated types to include docker-compose in the addons schema.
…ps/web/Dockerfile.hbs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
feat: add docker-compose addon support with templates and validation
|
@Crimson341 is attempting to deploy a commit to the Better T Stack Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds Docker Compose as a new addon and wires it through CLI prompts, compatibility/validation (backend/runtime-aware), templates, and tests; introduces multiple Docker-related templates and updates template embedding and types to include the new addon. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (5)
apps/cli/test/addons.test.ts (1)
1-3: PreferBun.filefor existence checks instead ofnode:fs.
Line 1-3 importsexistsSync, and multiple checks use it; switching toBun.file(...).exists()keeps tests aligned with Bun-first tooling.♻️ Suggested refactor
-import { existsSync } from "node:fs"; import { join } from "node:path"; import type { Addons, Frontend } from "../src"; import { expectError, expectSuccess, runTRPCTest, type TestConfig } from "./test-utils"; +async function fileExists(path: string) { + return Bun.file(path).exists(); +} @@ - expect(existsSync(dockerComposeYml)).toBe(true); + expect(await fileExists(dockerComposeYml)).toBe(true); @@ - expect(existsSync(serverDockerfile)).toBe(true); + expect(await fileExists(serverDockerfile)).toBe(true); @@ - expect(existsSync(webDockerfile)).toBe(true); + expect(await fileExists(webDockerfile)).toBe(true); @@ - expect(existsSync(dockerComposeYml)).toBe(true); - expect(existsSync(webDockerfile)).toBe(true); + expect(await fileExists(dockerComposeYml)).toBe(true); + expect(await fileExists(webDockerfile)).toBe(true); @@ - expect(existsSync(rootDockerignore)).toBe(true); + expect(await fileExists(rootDockerignore)).toBe(true);Based on learnings: Applies to **/*.{ts,tsx,js,jsx} : Use
Bun.fileovernode:fsin Bun projects.Also applies to: 347-349, 372-374, 398-399, 428-429, 453-454
packages/template-generator/templates/addons/docker-compose/apps/server/Dockerfile.hbs (2)
1-43: Use explicit runtime branches instead of a genericelse.
Consider switching the node branch to{{else if (eq runtime "node")}}so the conditions are explicit.🔧 Proposed tweak
-{{else}} +{{else if (eq runtime "node")}} @@ -{{/if}} +{{/if}}As per coding guidelines: In Handlebars templates, avoid generic if/else blocks. Write explicit conditions, such as: use if (eq orm "prisma") for Prisma, and else if (eq orm "drizzle") for Drizzle.
26-33: Confirm npm usage aligns with Bun-only tooling policy.
Line 27-33 usesnpm ci/npm run build; repo guidance prefers Bun for install/build. If Bun-only is intended here, consider switching to Bun-based install/build (e.g., bun image or installing bun in the node stage); otherwise document the exception.Based on learnings: Use
bunfor all operations (install, build, lint, dev server). Do not usenpm,yarn, orpnpm.packages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.vite.hbs (1)
10-10: Consider pinning pnpm version for reproducible builds.Using
pnpm@latestmay lead to inconsistent builds over time as pnpm versions change. Consider pinning to a specific version or using a version range.🔧 Proposed fix
-RUN corepack enable && corepack prepare pnpm@latest --activate +RUN corepack enable && corepack prepare pnpm@9 --activatepackages/template-generator/templates/addons/docker-compose/apps/web/Dockerfile.next.hbs (1)
30-30: Consider pinning pnpm version for build reproducibility.Using
pnpm@latestcould lead to inconsistent builds if pnpm releases a breaking change. Consider pinning to a specific version or major version range.Proposed fix
-RUN corepack enable && corepack prepare pnpm@latest --activate +RUN corepack enable && corepack prepare pnpm@9 --activate
| serverDeploy: "cloudflare", | ||
| serverDeploy: "alchemy", // Workers runtime requires server deployment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix duplicate serverDeploy key in the Workers-runtime test.
Line 254-255 defines serverDeploy twice; the latter overwrites the former and "alchemy" isn’t a valid ServerDeploy value, so the test may fail for the wrong reason. Keep a single valid value (likely "cloudflare").
🛠️ Proposed fix
- serverDeploy: "cloudflare",
- serverDeploy: "alchemy", // Workers runtime requires server deployment
+ serverDeploy: "cloudflare", // Workers runtime requires server deployment📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| serverDeploy: "cloudflare", | |
| serverDeploy: "alchemy", // Workers runtime requires server deployment | |
| serverDeploy: "cloudflare", // Workers runtime requires server deployment |
🧰 Tools
🪛 Biome (2.3.13)
[error] 254-254: This property is later overwritten by an object member with the same name.
Overwritten with this property.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property.
(lint/suspicious/noDuplicateObjectKeys)
| if (config.frontend.includes("next")) { | ||
| processTemplatesFromPrefix( | ||
| vfs, | ||
| templates, | ||
| "addons/docker-compose/apps/web", | ||
| "apps/web", | ||
| config, | ||
| ); | ||
| } else if ( | ||
| config.frontend.some((f) => | ||
| [ | ||
| "tanstack-router", | ||
| "react-router", | ||
| "tanstack-start", | ||
| "solid", | ||
| "svelte", | ||
| "nuxt", | ||
| "astro", | ||
| ].includes(f), | ||
| ) | ||
| ) { | ||
| processTemplatesFromPrefix( | ||
| vfs, | ||
| templates, | ||
| "addons/docker-compose/apps/web", | ||
| "apps/web", | ||
| config, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find and read the addons.ts file to verify the duplication claim
fd -t f "addons.ts" packages/template-generator/src/template-handlers/Repository: AmanVarshney01/create-better-t-stack
Length of output: 139
🏁 Script executed:
#!/bin/bash
# Check the structure of docker-compose addon templates
fd -t f . packages/template-generator/templates/addons/docker-compose/apps/web/ 2>/dev/null | head -20Repository: AmanVarshney01/create-better-t-stack
Length of output: 425
🏁 Script executed:
#!/bin/bash
# Read the addons.ts file to understand full context
if [ -f "packages/template-generator/src/template-handlers/addons.ts" ]; then
wc -l packages/template-generator/src/template-handlers/addons.ts
fiRepository: AmanVarshney01/create-better-t-stack
Length of output: 142
🏁 Script executed:
#!/bin/bash
# Read the relevant section of addons.ts (lines 30-80)
sed -n '30,80p' packages/template-generator/src/template-handlers/addons.tsRepository: AmanVarshney01/create-better-t-stack
Length of output: 1383
🏁 Script executed:
#!/bin/bash
# Check how processTemplatesFromPrefix function works - look for its definition
rg -A 20 "function processTemplatesFromPrefix|const processTemplatesFromPrefix" packages/template-generator/src/template-handlers/Repository: AmanVarshney01/create-better-t-stack
Length of output: 2112
🏁 Script executed:
#!/bin/bash
# Check the transformFilename function to see if it filters templates
rg -B 5 -A 15 "function transformFilename|const transformFilename" packages/template-generator/src/template-handlers/utils.tsRepository: AmanVarshney01/create-better-t-stack
Length of output: 62
🏁 Script executed:
#!/bin/bash
# Check the full processTemplatesFromPrefix to understand template filtering
rg -A 40 "export function processTemplatesFromPrefix" packages/template-generator/src/template-handlers/utils.tsRepository: AmanVarshney01/create-better-t-stack
Length of output: 1122
🏁 Script executed:
#!/bin/bash
# Verify what templates actually exist in the web addon directory
find packages/template-generator/templates/addons/docker-compose/apps/web -type f -exec basename {} \;Repository: AmanVarshney01/create-better-t-stack
Length of output: 167
🏁 Script executed:
#!/bin/bash
# Check the transformFilename function
rg -B 5 -A 20 "function transformFilename|const transformFilename" packages/template-generator/src/template-handlers/utils.tsRepository: AmanVarshney01/create-better-t-stack
Length of output: 62
🏁 Script executed:
#!/bin/bash
# Check if there's any config-based filtering in the template processing
rg -B 5 -A 10 "processTemplateString" packages/template-generator/src/template-handlers/utils.ts | head -40Repository: AmanVarshney01/create-better-t-stack
Length of output: 1375
🏁 Script executed:
#!/bin/bash
# Find and read the template-processor to understand transformFilename
fd -t f "template-processor" packages/template-generator/src/core/Repository: AmanVarshney01/create-better-t-stack
Length of output: 138
🏁 Script executed:
#!/bin/bash
# Read the template-processor file
if [ -f "packages/template-generator/src/core/template-processor.ts" ]; then
cat -n packages/template-generator/src/core/template-processor.ts
fiRepository: AmanVarshney01/create-better-t-stack
Length of output: 2095
Fix template selection to prevent writing all Dockerfile variants regardless of frontend type.
The code processes all templates under the prefix addons/docker-compose/apps/web for both Next.js and Vite frontends. Since three Dockerfile templates exist in that directory (Dockerfile.hbs, Dockerfile.next.hbs, Dockerfile.vite.hbs), both branches will output all three to apps/web/ (as Dockerfile, Dockerfile.next, Dockerfile.vite).
To output only the appropriate Dockerfile for each frontend type, use separate template prefixes or implement template filtering:
Example: Using separate prefixes for each frontend type
- // Place web Dockerfile based on frontend
- if (config.frontend.includes("next")) {
- processTemplatesFromPrefix(
- vfs,
- templates,
- "addons/docker-compose/apps/web",
- "apps/web",
- config,
- );
- } else if (
- config.frontend.some((f) =>
- [
- "tanstack-router",
- "react-router",
- "tanstack-start",
- "solid",
- "svelte",
- "nuxt",
- "astro",
- ].includes(f),
- )
- ) {
- processTemplatesFromPrefix(
- vfs,
- templates,
- "addons/docker-compose/apps/web",
- "apps/web",
- config,
- );
- }
+ // Place web Dockerfile based on frontend
+ if (config.frontend.includes("next")) {
+ processTemplatesFromPrefix(
+ vfs,
+ templates,
+ "addons/docker-compose/apps/web/next",
+ "apps/web",
+ config,
+ );
+ } else if (
+ config.frontend.some((f) =>
+ [
+ "tanstack-router",
+ "react-router",
+ "tanstack-start",
+ "solid",
+ "svelte",
+ "nuxt",
+ "astro",
+ ].includes(f),
+ )
+ ) {
+ processTemplatesFromPrefix(
+ vfs,
+ templates,
+ "addons/docker-compose/apps/web/vite",
+ "apps/web",
+ config,
+ );
+ }This requires reorganizing the template directory structure to have addons/docker-compose/apps/web/next/ and addons/docker-compose/apps/web/vite/ subdirectories with their respective Dockerfile templates.
| healthcheck: | ||
| test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] | ||
| interval: 10s | ||
| timeout: 5s | ||
| retries: 5 | ||
| restart: unless-stopped | ||
|
|
||
| volumes: | ||
| mysql_data: | ||
| {{/if}} | ||
| {{#if (eq database "mongodb")}} | ||
| db: | ||
| image: mongo:7 | ||
| container_name: {{projectName}}-mongodb | ||
| environment: | ||
| MONGO_INITDB_ROOT_USERNAME: root | ||
| MONGO_INITDB_ROOT_PASSWORD: password | ||
| MONGO_INITDB_DATABASE: {{projectName}} | ||
| ports: | ||
| - "27017:27017" | ||
| volumes: | ||
| - mongo_data:/data/db | ||
| healthcheck: | ||
| test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] | ||
| interval: 10s | ||
| timeout: 5s | ||
| retries: 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
official MySQL 8 Docker image mysqladmin ping authentication requirements
💡 Result:
In the official MySQL 8 Docker image (mysql:8.x), mysqladmin ping is just a client connection attempt, so its authentication requirements follow normal MySQL client rules:
- If the MySQL account you connect with has a password, you must provide one (e.g.,
-p.../--password=..., an option file, etc.). If you don’t specify any password option, mysqladmin sends no password. [1] - In the official container,
rootnormally has a password becauseMYSQL_ROOT_PASSWORDis mandatory unless you explicitly allow an empty password (e.g.,MYSQL_ALLOW_EMPTY_PASSWORD=yes). [2] - Important gotcha:
mysqladmin pingreturns exit code 0 even for “Access denied” (it treats that as “server is up but refused login”), so it may not be a reliable auth/ready check unless you also check output or use a different command. [1] - If your user authenticates with
caching_sha2_password(common in MySQL 8), and you connect over TCP without TLS, the client may need RSA key options like--get-server-public-key/--server-public-key-path(mysqladmin supports these). [1]
Practical healthcheck pattern (auth + readiness): use credentials and force TCP (often combined with status or mysql -e 'SELECT 1') rather than relying on ping alone. [1]
Sources: MySQL mysqladmin reference (password behavior, ping exit status, RSA key options) [1]; official MySQL Docker image docs (root password requirement / empty password option) [2].
References:
[1] (dev.mysql.com)
[2] (hub.docker.com)
Citations:
- 1: https://dev.mysql.com/doc/refman/8.0/en/mysqladmin.html?utm_source=openai
- 2: https://hub.docker.com/_/mysql?utm_source=openai
🌐 Web query:
official MongoDB Docker image mongosh authentication MONGO_INITDB_ROOT_USERNAME
💡 Result:
In the official mongo Docker image, setting:
MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD
will (on first initialization of an empty /data/db) create a root user in the admin authentication database and start mongod with auth enabled. If the data directory already contains a DB (e.g., you reused a volume), these env vars do nothing. [1]
Connect with mongosh (correct auth DB)
Use --authenticationDatabase admin:
mongosh --host <container-or-host> -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase adminExample from the official docs (connecting to a target DB after auth):
mongosh --host some-mongo -u mongoadmin -p secret --authenticationDatabase admin some-db[1]
Connect via URI (equivalent)
Make sure authSource=admin:
mongodb://<user>:<pass>@<host>:27017/<db>?authSource=admin
Source: Official MongoDB Docker image documentation on Docker Hub. [1]
🌐 Web query:
Docker healthcheck mysqladmin authentication credentials
💡 Result:
mysqladmin ping is a poor Docker healthcheck when you care about authentication, because it exits 0 even on “Access denied” (it only tells you the server is running, not that your credentials work). (dev.mysql.com)
Use a healthcheck that actually authenticates
Option A (recommended): mysqladmin status over TCP with env vars
healthcheck:
test: ["CMD-SHELL", "mysqladmin status -u$$MYSQL_USER -p$$MYSQL_PASSWORD --protocol=TCP"]
interval: 5s
timeout: 5s
retries: 10CMD-SHELLis needed so env vars expand.- In Compose, use
$$so$MYSQL_PASSWORDisn’t eaten by YAML/Compose interpolation. --protocol=TCPhelps avoid false positives during the official MySQL image init phase (temporary server / socket behavior). (stackoverflow.com)
Option B: run a real query
healthcheck:
test: ["CMD-SHELL", "mysql -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD -e 'SELECT 1' > /dev/null"]This pattern is commonly used specifically because mysqladmin ping can claim “healthy” too early / incorrectly. (stackoverflow.com)
If you must use mysqladmin ping
You still need to pass credentials (and you still won’t get a failing exit code on auth errors):
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD --silent"]But again: exit code can be 0 even with “Access denied”. (dev.mysql.com)
Avoid putting the password on the command line (safer)
Put credentials in an option file and reference it:
mysqladminsupports--defaults-extra-file(dev.mysql.com)- The MySQL docs warn that command-line passwords can be visible to other users/process listings. (dev.mysql.com)
Citations:
- 1: https://dev.mysql.com/doc/refman/8.4/en/mysqladmin.html
- 2: https://stackoverflow.com/questions/42567475/docker-compose-check-if-mysql-connection-is-ready?utm_source=openai
- 3: https://stackoverflow.com/questions/42567475/docker-compose-check-if-mysql-connection-is-ready?utm_source=openai
- 4: https://dev.mysql.com/doc/refman/8.4/en/mysqladmin.html
- 5: https://dev.mysql.com/doc/refman/8.4/en/mysqladmin.html
- 6: https://dev.mysql.com/doc/refman/8.4/en/mysqladmin.html
Healthchecks need credentials AND more reliable authentication checks.
The issue is real but more nuanced:
- MySQL:
mysqladmin pingreturns exit code 0 even on "Access denied" (just verifies the server is running, not that credentials work). WithMYSQL_ROOT_PASSWORDset, this won't detect auth failures. - MongoDB: With
MONGO_INITDB_ROOT_USERNAMEandMONGO_INITDB_ROOT_PASSWORD, auth is enabled andmongoshrequires credentials.
However, the suggested fixes are incomplete:
- MySQL fix: Use
mysqladmin statusormysql -e 'SELECT 1'instead ofpingfor reliable authentication verification. - MongoDB fix: Must include
--authenticationDatabase adminsince the root user is created in theadmindatabase.
✅ Corrected fix
healthcheck:
- test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+ test: ["CMD-SHELL", "mysqladmin status -h localhost -u$${MYSQL_USER} -p$${MYSQL_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
healthcheck:
- test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
+ test: ["CMD-SHELL", "mongosh --username $${MONGO_INITDB_ROOT_USERNAME} --password $${MONGO_INITDB_ROOT_PASSWORD} --authenticationDatabase admin --eval \"db.adminCommand('ping')\""]
interval: 10s
timeout: 5s
retries: 5| COPY --from=builder /app/dist /usr/share/nginx/html | ||
| COPY --from=builder /app/dist /usr/share/nginx/html |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate COPY instruction.
Line 38 is an exact duplicate of line 37. This appears to be a copy-paste error.olic
Proposed fix
FROM nginx:alpine AS runner
COPY --from=builder /app/dist /usr/share/nginx/html
-COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| COPY --from=builder /app/dist /usr/share/nginx/html | |
| COPY --from=builder /app/dist /usr/share/nginx/html | |
| FROM nginx:alpine AS runner | |
| COPY --from=builder /app/dist /usr/share/nginx/html | |
| COPY nginx.conf /etc/nginx/conf.d/default.conf | |
| CMD ["nginx", "-g", "daemon off;"] |
| environment: | ||
| POSTGRES_DB: {{projectName}} | ||
| POSTGRES_USER: postgres | ||
| POSTGRES_PASSWORD: password |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document that default credentials should be changed for production.
The hardcoded passwords (password, rootpassword) are suitable for development but pose a security risk if accidentally deployed to production. Consider adding a comment in the generated file or documentation.
📝 Proposed comment addition
db:
image: postgres:16-alpine
container_name: {{projectName}}-postgres
- environment:
+ # WARNING: Change these credentials before deploying to production
+ environment:
POSTGRES_DB: {{projectName}}
POSTGRES_USER: postgres
POSTGRES_PASSWORD: passwordThis same pattern applies to the MySQL (lines 72-76) and MongoDB (lines 95-98) sections.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
| .turbo | ||
| .DS_Store | ||
| `], | ||
| ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{`#if` (includes frontend "next")}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical syntax error: extra backticks break the template literal.
The template string has errant backticks around #if that break JavaScript parsing. Compare this line to line 223 (Dockerfile.next.hbs) which correctly uses {{#if`` without interrupting backticks.
🐛 Proposed fix
- ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{`#if` (includes frontend "next")}}
+ ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{`#if` (includes frontend "next")}}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{`#if` (includes frontend "next")}} | |
| ["addons/docker-compose/apps/web/Dockerfile.hbs", `{{`#if` (includes frontend "next")}} |
🧰 Tools
🪛 Biome (2.3.13)
[error] 181-181: expected , but instead found #
Remove #
(parse)
[error] 181-181: expected , but instead found ```
Remove `
(parse)
docker-composeaddon that containerizes the full application stack (frontend, backend, database)Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.