create-tigra 2.8.0 → 3.0.2

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.
Files changed (53) hide show
  1. package/README.md +10 -3
  2. package/bin/create-tigra.js +77 -37
  3. package/package.json +5 -5
  4. package/template/_claude/commands/create-server.md +8 -2
  5. package/template/_claude/rules/client/01-project-structure.md +12 -0
  6. package/template/_claude/rules/client/03-data-and-state.md +1 -1
  7. package/template/_claude/rules/client/04-design-system.md +23 -0
  8. package/template/_claude/rules/client/07-deployment.md +99 -0
  9. package/template/_claude/rules/client/08-lockfile-cross-platform.md +79 -0
  10. package/template/_claude/rules/client/core.md +1 -0
  11. package/template/_claude/rules/global/core.md +20 -1
  12. package/template/_claude/rules/global/investigation-before-conclusions.md +57 -0
  13. package/template/_claude/rules/server/core.md +2 -0
  14. package/template/_claude/rules/server/deployment.md +78 -0
  15. package/template/client/next.config.ts +12 -2
  16. package/template/client/package-lock.json +12345 -0
  17. package/template/client/package.json +3 -2
  18. package/template/client/src/components/common/SafeImage.tsx +2 -1
  19. package/template/client/src/lib/api/axios.config.ts +19 -4
  20. package/template/client/src/middleware.ts +7 -0
  21. package/template/gitignore +1 -0
  22. package/template/server/.env.example +248 -194
  23. package/template/server/.env.example.production +221 -168
  24. package/template/server/Dockerfile +29 -5
  25. package/template/server/docker-compose.yml +32 -4
  26. package/template/server/package-lock.json +6544 -6823
  27. package/template/server/package.json +76 -75
  28. package/template/server/prisma/seed.ts +20 -4
  29. package/template/server/src/app.ts +316 -271
  30. package/template/server/src/config/env.ts +150 -99
  31. package/template/server/src/config/rate-limit.config.ts +16 -0
  32. package/template/server/src/libs/__tests__/auth-path.test.ts +24 -0
  33. package/template/server/src/libs/__tests__/client-ip.test.ts +121 -0
  34. package/template/server/src/libs/__tests__/http.test.ts +23 -9
  35. package/template/server/src/libs/__tests__/ip-block.test.ts +62 -0
  36. package/template/server/src/libs/__tests__/origin-check.test.ts +53 -0
  37. package/template/server/src/libs/__tests__/url-safety.test.ts +80 -0
  38. package/template/server/src/libs/auth-path.ts +14 -0
  39. package/template/server/src/libs/auth.ts +6 -16
  40. package/template/server/src/libs/client-ip.ts +77 -0
  41. package/template/server/src/libs/cookies.ts +1 -1
  42. package/template/server/src/libs/duration.ts +30 -0
  43. package/template/server/src/libs/ip-block.ts +220 -206
  44. package/template/server/src/libs/origin-check.ts +38 -0
  45. package/template/server/src/libs/query-counter.ts +59 -0
  46. package/template/server/src/libs/redis.ts +1 -1
  47. package/template/server/src/libs/url-safety.ts +121 -0
  48. package/template/server/src/modules/auth/__tests__/auth.service.test.ts +274 -44
  49. package/template/server/src/modules/auth/auth.controller.ts +128 -127
  50. package/template/server/src/modules/auth/auth.repo.ts +2 -0
  51. package/template/server/src/modules/auth/auth.service.ts +103 -12
  52. package/template/server/src/test/setup.ts +22 -2
  53. package/template/server/vitest.config.ts +43 -43
@@ -1,168 +1,221 @@
1
- # ===================================================================
2
- # PRODUCTION ENVIRONMENT CONFIGURATION
3
- # ===================================================================
4
- # This file contains production-optimized settings for high-traffic
5
- # deployments (10K-100K users/day). Copy to .env and customize.
6
- #
7
- # COOLIFY DEPLOYMENT NOTE:
8
- # When adding environment variables in Coolify, do NOT check
9
- # "Available at Buildtime" for any variable unless explicitly noted.
10
- # The server Dockerfile handles build-time config internally.
11
- # Secrets (DATABASE_URL, JWT_SECRET, COOKIE_SECRET, REDIS_URL) must
12
- # NEVER be available at buildtime — they get baked into the image layer.
13
- #
14
- # ===================================================================
15
-
16
- # ===================================================================
17
- # APPLICATION
18
- # ===================================================================
19
-
20
- # COOLIFY: Do NOT check "Available at Buildtime" — the Dockerfile
21
- # sets NODE_ENV=development during build to ensure devDependencies install.
22
- NODE_ENV=production
23
- PORT=3000
24
- HOST=0.0.0.0
25
-
26
- # ===================================================================
27
- # DATABASE (MySQL 8.0+)
28
- # ===================================================================
29
-
30
- # Production database connection
31
- # CRITICAL: Use secure credentials, SSL/TLS, and private network
32
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
33
- DATABASE_URL="mysql://prod_user:SECURE_PASSWORD@db.internal:3306/prod_db?ssl=true"
34
-
35
- # Connection pool for high traffic (10K-100K users/day)
36
- # Recommended: 20-50 connections for production
37
- # Monitor and adjust based on load testing results
38
- DATABASE_POOL_MIN=10
39
- DATABASE_POOL_MAX=50
40
-
41
- # ===================================================================
42
- # REDIS
43
- # ===================================================================
44
-
45
- # Production Redis instance
46
- # CRITICAL: Use authentication and private network
47
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
48
- REDIS_URL="redis://:REDIS_PASSWORD@redis.internal:6379"
49
-
50
- # Production retry settings
51
- REDIS_MAX_RETRIES=5
52
- REDIS_CONNECT_TIMEOUT=5000
53
-
54
- # ===================================================================
55
- # RATE LIMITING
56
- # ===================================================================
57
-
58
- # Always enabled in production
59
- RATE_LIMIT_ENABLED=true
60
-
61
- # Production: keep at 1 (default limits are tuned for production)
62
- RATE_LIMIT_MULTIPLIER=1
63
-
64
- # Tight limits for sensitive auth routes in production
65
- RATE_LIMIT_AUTH_LOGIN_MAX=10
66
- RATE_LIMIT_AUTH_REGISTER_MAX=5
67
-
68
- # ===================================================================
69
- # ACCOUNT ACTIVATION
70
- # ===================================================================
71
-
72
- # Require account verification before users can log in
73
- # Set to true in production for security
74
- REQUIRE_USER_VERIFICATION=true
75
-
76
- # ===================================================================
77
- # FILE UPLOAD
78
- # ===================================================================
79
-
80
- # Maximum file upload size in MB
81
- MAX_FILE_SIZE_MB=10
82
-
83
- # COOLIFY PERSISTENT STORAGE (required for uploads to survive redeployments):
84
- # Go to your service in Coolify Storages Add Volume Mount:
85
- # Name: uploads (or <project-name>-uploads)
86
- # Source Path: (leave empty Coolify manages the Docker volume)
87
- # Destination Path: /app/uploads
88
- # Without this, all uploaded files are lost on every redeployment.
89
-
90
- # ===================================================================
91
- # JWT AUTHENTICATION
92
- # ===================================================================
93
-
94
- # CRITICAL: Generate a cryptographically secure secret
95
- # Example: openssl rand -base64 48
96
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
97
- JWT_SECRET="YOUR_PRODUCTION_JWT_SECRET_MUST_BE_AT_LEAST_32_CHARS_LONG"
98
-
99
- # Production token expiry (tighter security)
100
- JWT_ACCESS_EXPIRY="15m"
101
- JWT_REFRESH_EXPIRY="7d"
102
-
103
- # Cookie signing secret (separate from JWT for defense-in-depth)
104
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
105
- COOKIE_SECRET="YOUR_PRODUCTION_COOKIE_SECRET_MUST_BE_AT_LEAST_32_CHARS"
106
-
107
- # ===================================================================
108
- # CORS
109
- # ===================================================================
110
-
111
- # REQUIRED in production - your frontend domain(s)
112
- # Multiple origins separated by commas
113
- CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com"
114
-
115
- # Cookie domain for cross-origin deployments
116
- # REQUIRED when client and API are on different subdomains:
117
- # Client: https://app.example.com | API: https://api.example.com
118
- # Set COOKIE_DOMAIN=".example.com" (leading dot = all subdomains)
119
- # This enables cookies to be shared between client and API subdomains.
120
- # Without it, login will silently fail (server returns 200 but browser drops cookies).
121
- COOKIE_DOMAIN=".yourdomain.com"
122
-
123
- # ===================================================================
124
- # EMAIL (Resend)
125
- # ===================================================================
126
-
127
- # Resend API key for transactional emails
128
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
129
- RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
130
-
131
- # Sender email — must be from a verified domain in Resend
132
- RESEND_FROM_EMAIL="noreply@yourdomain.com"
133
-
134
- # Frontend URL for email links (password reset, verification, etc.)
135
- CLIENT_URL="https://yourdomain.com"
136
-
137
- # ===================================================================
138
- # LOGGING
139
- # ===================================================================
140
-
141
- # Production: info or warn (reduces log volume)
142
- # Use warn to reduce costs in high-traffic scenarios
143
- LOG_LEVEL=info
144
-
145
- # ===================================================================
146
- # ERROR TRACKING
147
- # ===================================================================
148
-
149
- # Sentry for production error monitoring
150
- # Get your DSN from: https://sentry.io/settings/projects/
151
- SENTRY_DSN="https://YOUR_PUBLIC_KEY@o0.ingest.sentry.io/YOUR_PROJECT_ID"
152
-
153
- # ===================================================================
154
- # PRODUCTION CHECKLIST
155
- # ===================================================================
156
- # [ ] DATABASE_URL uses secure credentials and SSL
157
- # [ ] JWT_SECRET is a strong random string (48+ chars)
158
- # [ ] CORS_ORIGIN is set to your exact frontend URL(s)
159
- # [ ] COOKIE_DOMAIN is set if client and API are on different subdomains
160
- # [ ] REDIS_URL uses authentication if Redis is exposed
161
- # [ ] LOG_LEVEL is set to info or warn
162
- # [ ] SENTRY_DSN is configured for error tracking
163
- # [ ] Database connection pool tested under load
164
- # [ ] All secrets are stored in environment variables, not in code
165
- # [ ] SSL/TLS certificates are configured
166
- # [ ] Firewall rules restrict access to database and Redis
167
- # [ ] In Coolify: NO secrets have "Available at Buildtime" checked
168
- # [ ] In Coolify: NODE_ENV does NOT have "Available at Buildtime" checked
1
+ # ===================================================================
2
+ # PRODUCTION ENVIRONMENT CONFIGURATION
3
+ # ===================================================================
4
+ # This file contains production-optimized settings for high-traffic
5
+ # deployments (10K-100K users/day). Copy to .env and customize.
6
+ #
7
+ # COOLIFY DEPLOYMENT NOTE:
8
+ # When adding environment variables in Coolify, do NOT check
9
+ # "Available at Buildtime" for any variable unless explicitly noted.
10
+ # The server Dockerfile handles build-time config internally.
11
+ # Secrets (DATABASE_URL, JWT_SECRET, COOKIE_SECRET, REDIS_URL) must
12
+ # NEVER be available at buildtime — they get baked into the image layer.
13
+ #
14
+ # ===================================================================
15
+
16
+ # ===================================================================
17
+ # APPLICATION
18
+ # ===================================================================
19
+
20
+ # COOLIFY: Do NOT check "Available at Buildtime" — the Dockerfile
21
+ # sets NODE_ENV=development during build to ensure devDependencies install.
22
+ NODE_ENV=production
23
+ PORT=3000
24
+ HOST=0.0.0.0
25
+
26
+ # ===================================================================
27
+ # SERVER TIMEOUTS
28
+ # ===================================================================
29
+
30
+ # Fastify request timeout in milliseconds (default: 30000 = 30s)
31
+ # Long-running routes (LLM calls, big exports) may need 180000+ (180s).
32
+ # CRITICAL: the reverse proxy (Nginx/Coolify) timeout must be raised to
33
+ # match, or the proxy cuts the connection before the server does.
34
+ REQUEST_TIMEOUT_MS=30000
35
+
36
+ # Fastify connection timeout in milliseconds (default: 60000 = 60s)
37
+ CONNECTION_TIMEOUT_MS=60000
38
+
39
+ # ===================================================================
40
+ # DATABASE (MySQL 8.0+)
41
+ # ===================================================================
42
+
43
+ # Production database connection
44
+ # CRITICAL: Use secure credentials, SSL/TLS, and private network
45
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
46
+ DATABASE_URL="mysql://prod_user:SECURE_PASSWORD@db.internal:3306/prod_db?ssl=true"
47
+
48
+ # Connection pool for high traffic (10K-100K users/day)
49
+ # Recommended: 20-50 connections for production
50
+ # Monitor and adjust based on load testing results
51
+ DATABASE_POOL_MIN=10
52
+ DATABASE_POOL_MAX=50
53
+
54
+ # ===================================================================
55
+ # REDIS
56
+ # ===================================================================
57
+
58
+ # Production Redis instance
59
+ # CRITICAL: Use authentication and private network
60
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
61
+ REDIS_URL="redis://:REDIS_PASSWORD@redis.internal:6379"
62
+
63
+ # Production retry settings
64
+ REDIS_MAX_RETRIES=5
65
+ REDIS_CONNECT_TIMEOUT=5000
66
+
67
+ # ===================================================================
68
+ # RATE LIMITING
69
+ # ===================================================================
70
+
71
+ # Always enabled in production
72
+ RATE_LIMIT_ENABLED=true
73
+
74
+ # Production: keep at 1 (default limits are tuned for production)
75
+ RATE_LIMIT_MULTIPLIER=1
76
+
77
+ # Tight limits for sensitive auth routes in production
78
+ RATE_LIMIT_AUTH_LOGIN_MAX=10
79
+ RATE_LIMIT_AUTH_REGISTER_MAX=5
80
+
81
+ # Trust Cloudflare's CF-Connecting-IP header for the real client IP (default: false)
82
+ # Used ONLY for rate-limiting and IP auto-block decisions. Behind Cloudflare,
83
+ # request.ip is a CF edge IP without this, all users behind one edge collapse
84
+ # onto a single IP and can rate-limit or auto-ban each other.
85
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
86
+ # SECURITY: the header is client-spoofable. Set true ONLY when this origin
87
+ # accepts traffic exclusively via Cloudflare (direct origin access is blocked
88
+ # at the firewall/network so clients cannot reach it bypassing CF).
89
+ # Note: the left-most X-Forwarded-For entry is now trusted as the client IP
90
+ # regardless of this flag (covers grey-cloud / DNS-only), so the origin must be
91
+ # proxy-locked (firewall direct access) in production either way.
92
+ TRUST_CLOUDFLARE=false
93
+
94
+ # ===================================================================
95
+ # IP AUTO-BLOCK
96
+ # ===================================================================
97
+
98
+ # Rate-limit violations before an IP is auto-blocked for every route.
99
+ # Keep HIGH enough that a retry-looping legitimate client or a NAT'd
100
+ # office sharing one egress IP cannot self-ban (sustained abuse only).
101
+ # See src/config/rate-limit.config.ts before lowering.
102
+ IP_AUTO_BLOCK_THRESHOLD=20
103
+
104
+ # Sliding window for counting violations, in seconds (5 minutes)
105
+ IP_AUTO_BLOCK_WINDOW_SECONDS=300
106
+
107
+ # How long an auto-blocked IP stays blocked, in seconds (1 hour)
108
+ IP_AUTO_BLOCK_DURATION_SECONDS=3600
109
+
110
+ # ===================================================================
111
+ # ACCOUNT ACTIVATION
112
+ # ===================================================================
113
+
114
+ # Require account verification before users can log in
115
+ # Set to true in production for security
116
+ REQUIRE_USER_VERIFICATION=true
117
+
118
+ # ===================================================================
119
+ # FILE UPLOAD
120
+ # ===================================================================
121
+
122
+ # Maximum file upload size in MB
123
+ MAX_FILE_SIZE_MB=10
124
+
125
+ # COOLIFY PERSISTENT STORAGE (required for uploads to survive redeployments):
126
+ # Go to your service in Coolify → Storages → Add Volume Mount:
127
+ # Name: uploads (or <project-name>-uploads)
128
+ # Source Path: (leave empty Coolify manages the Docker volume)
129
+ # Destination Path: /app/uploads
130
+ # Without this, all uploaded files are lost on every redeployment.
131
+
132
+ # ===================================================================
133
+ # JWT AUTHENTICATION
134
+ # ===================================================================
135
+
136
+ # CRITICAL: Generate a cryptographically secure secret
137
+ # Example: openssl rand -base64 48
138
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
139
+ JWT_SECRET="YOUR_PRODUCTION_JWT_SECRET_MUST_BE_AT_LEAST_32_CHARS_LONG"
140
+
141
+ # Production token expiry (tighter security)
142
+ JWT_ACCESS_EXPIRY="15m"
143
+ JWT_REFRESH_EXPIRY="7d"
144
+
145
+ # Cookie signing secret (separate from JWT for defense-in-depth)
146
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
147
+ COOKIE_SECRET="YOUR_PRODUCTION_COOKIE_SECRET_MUST_BE_AT_LEAST_32_CHARS"
148
+
149
+ # ===================================================================
150
+ # CORS
151
+ # ===================================================================
152
+
153
+ # REQUIRED in production - your frontend domain(s)
154
+ # Multiple origins separated by commas
155
+ CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com"
156
+
157
+ # Cookie domain for cross-origin deployments
158
+ # REQUIRED when client and API are on different subdomains:
159
+ # Client: https://app.example.com | API: https://api.example.com
160
+ # Set COOKIE_DOMAIN=".example.com" (leading dot = all subdomains)
161
+ # This enables cookies to be shared between client and API subdomains.
162
+ # Without it, login will silently fail (server returns 200 but browser drops cookies).
163
+ COOKIE_DOMAIN=".yourdomain.com"
164
+
165
+ # ===================================================================
166
+ # EMAIL (Resend)
167
+ # ===================================================================
168
+
169
+ # Resend API key for transactional emails
170
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
171
+ RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
172
+
173
+ # Sender email — must be from a verified domain in Resend
174
+ RESEND_FROM_EMAIL="noreply@yourdomain.com"
175
+
176
+ # Frontend URL for email links (password reset, verification, etc.)
177
+ CLIENT_URL="https://yourdomain.com"
178
+
179
+ # ===================================================================
180
+ # LOGGING
181
+ # ===================================================================
182
+
183
+ # Production: info or warn (reduces log volume)
184
+ # Use warn to reduce costs in high-traffic scenarios
185
+ LOG_LEVEL=info
186
+
187
+ # ===================================================================
188
+ # DATABASE SEEDING
189
+ # ===================================================================
190
+
191
+ # DO NOT SEED PRODUCTION. The seed script (npm run prisma:seed) creates
192
+ # well-known demo accounts (admin@example.com) and hard-refuses to run when
193
+ # NODE_ENV=production. These variables exist only to keep .env files in sync
194
+ # across environments — leave them commented out in production.
195
+ # SEED_ADMIN_PASSWORD=
196
+ # SEED_USER_PASSWORD=
197
+
198
+ # ===================================================================
199
+ # ERROR TRACKING
200
+ # ===================================================================
201
+
202
+ # Sentry for production error monitoring
203
+ # Get your DSN from: https://sentry.io/settings/projects/
204
+ SENTRY_DSN="https://YOUR_PUBLIC_KEY@o0.ingest.sentry.io/YOUR_PROJECT_ID"
205
+
206
+ # ===================================================================
207
+ # PRODUCTION CHECKLIST
208
+ # ===================================================================
209
+ # [ ] DATABASE_URL uses secure credentials and SSL
210
+ # [ ] JWT_SECRET is a strong random string (48+ chars)
211
+ # [ ] CORS_ORIGIN is set to your exact frontend URL(s)
212
+ # [ ] COOKIE_DOMAIN is set if client and API are on different subdomains
213
+ # [ ] REDIS_URL uses authentication if Redis is exposed
214
+ # [ ] LOG_LEVEL is set to info or warn
215
+ # [ ] SENTRY_DSN is configured for error tracking
216
+ # [ ] Database connection pool tested under load
217
+ # [ ] All secrets are stored in environment variables, not in code
218
+ # [ ] SSL/TLS certificates are configured
219
+ # [ ] Firewall rules restrict access to database and Redis
220
+ # [ ] In Coolify: NO secrets have "Available at Buildtime" checked
221
+ # [ ] In Coolify: NODE_ENV does NOT have "Available at Buildtime" checked
@@ -23,6 +23,18 @@ RUN if [ -f pnpm-lock.yaml ]; then \
23
23
  npm ci --omit=dev; \
24
24
  fi
25
25
 
26
+ # Add the Prisma CLI to the production node_modules so the runtime image can
27
+ # run `npx prisma migrate deploy` at container start (see CMD in stage 3).
28
+ # `prisma` is a devDependency (correct for local dev), so the prod-only install
29
+ # above does NOT include it — without this step, npx would try to download the
30
+ # CLI from the registry on every container boot. Pinned to the exact
31
+ # @prisma/client version the lockfile installed, so CLI and client never drift.
32
+ # --no-save keeps package.json and the lockfile untouched. Because this stage
33
+ # runs on the same Alpine (musl) base as the runtime stage, the engine binaries
34
+ # downloaded here are the correct ones for production.
35
+ RUN npm install --no-save --no-audit --no-fund \
36
+ "prisma@$(node -p "require('@prisma/client/package.json').version")"
37
+
26
38
  # ===================================================================
27
39
  # Stage 2: Build (compile TypeScript)
28
40
  # ===================================================================
@@ -69,7 +81,9 @@ RUN addgroup -g 1001 -S nodejs && \
69
81
 
70
82
  WORKDIR /app
71
83
 
72
- # Copy production dependencies from dependencies stage
84
+ # Copy production dependencies from dependencies stage.
85
+ # Includes the Prisma CLI (+ its engines) installed in stage 1, which the
86
+ # CMD below needs for `npx prisma migrate deploy`.
73
87
  COPY --from=dependencies --chown=nodejs:nodejs /app/node_modules ./node_modules
74
88
 
75
89
  # Copy generated Prisma client from builder stage (not present in prod deps)
@@ -91,12 +105,22 @@ USER nodejs
91
105
  # Expose port (matches default PORT in env.ts; override via PORT env var)
92
106
  EXPOSE 8000
93
107
 
94
- # Health check for container orchestration
95
- HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
108
+ # Health check for container orchestration.
109
+ # start-period is 60s because CMD runs `prisma migrate deploy` BEFORE the server
110
+ # starts listening — a slow migration must not be counted as unhealthy, or
111
+ # Coolify restart-loops the container mid-migration.
112
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
96
113
  CMD node -e "const p=process.env.PORT||8000;require('http').get('http://localhost:'+p+'/api/v1/live',(r)=>{process.exit(r.statusCode===200?0:1)})"
97
114
 
98
115
  # Use dumb-init to handle signals properly
99
116
  ENTRYPOINT ["dumb-init", "--"]
100
117
 
101
- # Start the application
102
- CMD ["node", "dist/server.js"]
118
+ # Start the application — apply any pending Prisma migrations first, then boot.
119
+ # Runs inside Coolify on every container start, where DATABASE_URL is reachable
120
+ # (it isn't from CI, which is why migrations belong here and not in a workflow).
121
+ # `migrate deploy` is idempotent: it applies only pending migrations and exits 0
122
+ # when there is nothing to do. Requirements, all satisfied above:
123
+ # - prisma CLI in ./node_modules (installed in stage 1, copied with node_modules)
124
+ # - ./prisma/schema.prisma + ./prisma/migrations (copied from builder)
125
+ # `exec` replaces the shell so node receives signals from dumb-init directly.
126
+ CMD ["sh", "-c", "npx prisma migrate deploy && exec node dist/server.js"]
@@ -1,5 +1,14 @@
1
1
  # WARNING: These credentials are for LOCAL DEVELOPMENT ONLY.
2
2
  # Change all passwords and secrets before using in any shared or production environment.
3
+ #
4
+ # Security posture:
5
+ # - All published ports are bound to 127.0.0.1 — nothing is reachable from the
6
+ # network (an unpassworded Redis or root-password MySQL on 0.0.0.0 is an open
7
+ # door on shared networks/VPS dev boxes). Don't remove the 127.0.0.1 prefix.
8
+ # - The admin UIs (phpMyAdmin, Redis Commander) are behind the "tools" profile
9
+ # and do NOT start by default:
10
+ # docker compose up -d → MySQL + Redis only
11
+ # docker compose --profile tools up -d → also phpMyAdmin + Redis Commander
3
12
 
4
13
  name: {{PROJECT_NAME}}
5
14
 
@@ -8,8 +17,19 @@ services:
8
17
  image: mysql:8.0
9
18
  container_name: {{PROJECT_NAME}}-mysql
10
19
  restart: unless-stopped
20
+ # Local-dev resource bounds. mem_limit is a cgroup ceiling (a runaway container is
21
+ # OOM-killed in isolation instead of wedging the whole WSL2/Docker engine); the flags
22
+ # dev-tune MySQL 8 to a small, fast-warming footprint. Production reaches MySQL as a
23
+ # separate Coolify resource via DATABASE_URL — none of this touches prod.
24
+ mem_limit: 900m
25
+ command:
26
+ - --innodb-buffer-pool-size=128M # default is fine for dev; keeps RSS small
27
+ - --performance-schema=OFF # saves ~hundreds of MB on its own
28
+ - --skip-name-resolve # no reverse-DNS per connection (faster, leaner)
29
+ - --max-connections=60 # well above DATABASE_POOL_MAX (10) + headroom
30
+ - --innodb-flush-log-at-trx-commit=2 # dev durability/throughput trade — not for prod
11
31
  ports:
12
- - '${MYSQL_PORT:-{{MYSQL_PORT}}}:3306'
32
+ - '127.0.0.1:${MYSQL_PORT:-{{MYSQL_PORT}}}:3306'
13
33
  environment:
14
34
  MYSQL_ROOT_PASSWORD: rootpassword
15
35
  MYSQL_DATABASE: {{DATABASE_NAME}}
@@ -27,8 +47,9 @@ services:
27
47
  image: phpmyadmin:latest
28
48
  container_name: {{PROJECT_NAME}}-phpmyadmin
29
49
  restart: unless-stopped
50
+ profiles: ["tools"]
30
51
  ports:
31
- - '${PHPMYADMIN_PORT:-{{PHPMYADMIN_PORT}}}:80'
52
+ - '127.0.0.1:${PHPMYADMIN_PORT:-{{PHPMYADMIN_PORT}}}:80'
32
53
  environment:
33
54
  PMA_HOST: mysql
34
55
  PMA_PORT: 3306
@@ -47,8 +68,14 @@ services:
47
68
  image: redis:7-alpine
48
69
  container_name: {{PROJECT_NAME}}-redis
49
70
  restart: unless-stopped
71
+ # Local-dev resource bounds. Layer two ceilings: Redis's own --maxmemory (150mb) trips
72
+ # FIRST and refuses writes loudly (noeviction — create-tigra also keeps rate-limit/queue
73
+ # keys in Redis, so silent LRU eviction would be a dev data-loss trap), while the 200m
74
+ # cgroup mem_limit sits above it as a hard backstop. --save "" drops RDB snapshot fork/IO.
75
+ mem_limit: 200m
76
+ command: ['redis-server', '--maxmemory', '150mb', '--maxmemory-policy', 'noeviction', '--save', '']
50
77
  ports:
51
- - '${REDIS_PORT:-{{REDIS_PORT}}}:6379'
78
+ - '127.0.0.1:${REDIS_PORT:-{{REDIS_PORT}}}:6379'
52
79
  volumes:
53
80
  - redis_data:/data
54
81
  networks:
@@ -63,8 +90,9 @@ services:
63
90
  image: rediscommander/redis-commander:latest
64
91
  container_name: {{PROJECT_NAME}}-redis-commander
65
92
  restart: unless-stopped
93
+ profiles: ["tools"]
66
94
  ports:
67
- - '${REDIS_COMMANDER_PORT:-{{REDIS_COMMANDER_PORT}}}:8081'
95
+ - '127.0.0.1:${REDIS_COMMANDER_PORT:-{{REDIS_COMMANDER_PORT}}}:8081'
68
96
  environment:
69
97
  REDIS_HOSTS: local:redis:6379
70
98
  depends_on: