create-tigra 3.0.0 → 3.0.3

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 (44) hide show
  1. package/package.json +1 -1
  2. package/template/client/.env.example +19 -0
  3. package/template/client/Dockerfile +3 -3
  4. package/template/client/next.config.ts +7 -42
  5. package/template/client/package-lock.json +1896 -146
  6. package/template/client/package.json +2 -0
  7. package/template/client/src/app/layout.tsx +10 -3
  8. package/template/client/src/app/providers.tsx +15 -2
  9. package/template/client/src/instrumentation-client.ts +30 -0
  10. package/template/client/src/instrumentation.ts +38 -0
  11. package/template/client/src/lib/env.ts +13 -2
  12. package/template/client/src/middleware.ts +105 -18
  13. package/template/server/.env.example +269 -236
  14. package/template/server/.env.example.production +236 -208
  15. package/template/server/Dockerfile +7 -5
  16. package/template/server/docker-compose.yml +17 -0
  17. package/template/server/package-lock.json +2136 -86
  18. package/template/server/package.json +6 -0
  19. package/template/server/src/app.ts +335 -303
  20. package/template/server/src/config/env.ts +171 -143
  21. package/template/server/src/config/rate-limit.config.ts +6 -0
  22. package/template/server/src/libs/__tests__/auth-path.test.ts +24 -0
  23. package/template/server/src/libs/__tests__/client-ip.test.ts +121 -0
  24. package/template/server/src/libs/__tests__/ip-block.test.ts +62 -0
  25. package/template/server/src/libs/__tests__/url-safety.test.ts +80 -0
  26. package/template/server/src/libs/auth-path.ts +14 -0
  27. package/template/server/src/libs/client-ip.ts +77 -0
  28. package/template/server/src/libs/ip-block.ts +220 -212
  29. package/template/server/src/libs/logger.ts +15 -0
  30. package/template/server/src/libs/observability/sentry.ts +42 -0
  31. package/template/server/src/libs/query-counter.ts +59 -0
  32. package/template/server/src/libs/requestLogger.ts +8 -2
  33. package/template/server/src/libs/storage/file-storage.service.ts +144 -16
  34. package/template/server/src/libs/url-safety.ts +121 -0
  35. package/template/server/src/modules/admin/__tests__/admin.integration.test.ts +128 -0
  36. package/template/server/src/modules/auth/__tests__/auth.integration.test.ts +138 -0
  37. package/template/server/src/modules/auth/auth.controller.ts +128 -127
  38. package/template/server/src/modules/files/__tests__/files.integration.test.ts +157 -0
  39. package/template/server/src/modules/files/files.controller.ts +180 -0
  40. package/template/server/src/modules/files/files.routes.ts +46 -0
  41. package/template/server/src/server.ts +6 -0
  42. package/template/server/src/test/integration.setup.ts +170 -0
  43. package/template/server/vitest.config.ts +10 -1
  44. package/template/server/vitest.integration.config.ts +50 -0
@@ -1,208 +1,236 @@
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
- # ===================================================================
82
- # IP AUTO-BLOCK
83
- # ===================================================================
84
-
85
- # Rate-limit violations before an IP is auto-blocked for every route.
86
- # Keep HIGH enough that a retry-looping legitimate client or a NAT'd
87
- # office sharing one egress IP cannot self-ban (sustained abuse only).
88
- # See src/config/rate-limit.config.ts before lowering.
89
- IP_AUTO_BLOCK_THRESHOLD=20
90
-
91
- # Sliding window for counting violations, in seconds (5 minutes)
92
- IP_AUTO_BLOCK_WINDOW_SECONDS=300
93
-
94
- # How long an auto-blocked IP stays blocked, in seconds (1 hour)
95
- IP_AUTO_BLOCK_DURATION_SECONDS=3600
96
-
97
- # ===================================================================
98
- # ACCOUNT ACTIVATION
99
- # ===================================================================
100
-
101
- # Require account verification before users can log in
102
- # Set to true in production for security
103
- REQUIRE_USER_VERIFICATION=true
104
-
105
- # ===================================================================
106
- # FILE UPLOAD
107
- # ===================================================================
108
-
109
- # Maximum file upload size in MB
110
- MAX_FILE_SIZE_MB=10
111
-
112
- # COOLIFY PERSISTENT STORAGE (required for uploads to survive redeployments):
113
- # Go to your service in Coolify → Storages → Add Volume Mount:
114
- # Name: uploads (or <project-name>-uploads)
115
- # Source Path: (leave empty Coolify manages the Docker volume)
116
- # Destination Path: /app/uploads
117
- # Without this, all uploaded files are lost on every redeployment.
118
-
119
- # ===================================================================
120
- # JWT AUTHENTICATION
121
- # ===================================================================
122
-
123
- # CRITICAL: Generate a cryptographically secure secret
124
- # Example: openssl rand -base64 48
125
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" this is a secret!
126
- JWT_SECRET="YOUR_PRODUCTION_JWT_SECRET_MUST_BE_AT_LEAST_32_CHARS_LONG"
127
-
128
- # Production token expiry (tighter security)
129
- JWT_ACCESS_EXPIRY="15m"
130
- JWT_REFRESH_EXPIRY="7d"
131
-
132
- # Cookie signing secret (separate from JWT for defense-in-depth)
133
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
134
- COOKIE_SECRET="YOUR_PRODUCTION_COOKIE_SECRET_MUST_BE_AT_LEAST_32_CHARS"
135
-
136
- # ===================================================================
137
- # CORS
138
- # ===================================================================
139
-
140
- # REQUIRED in production - your frontend domain(s)
141
- # Multiple origins separated by commas
142
- CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com"
143
-
144
- # Cookie domain for cross-origin deployments
145
- # REQUIRED when client and API are on different subdomains:
146
- # Client: https://app.example.com | API: https://api.example.com
147
- # Set COOKIE_DOMAIN=".example.com" (leading dot = all subdomains)
148
- # This enables cookies to be shared between client and API subdomains.
149
- # Without it, login will silently fail (server returns 200 but browser drops cookies).
150
- COOKIE_DOMAIN=".yourdomain.com"
151
-
152
- # ===================================================================
153
- # EMAIL (Resend)
154
- # ===================================================================
155
-
156
- # Resend API key for transactional emails
157
- # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
158
- RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
159
-
160
- # Sender email — must be from a verified domain in Resend
161
- RESEND_FROM_EMAIL="noreply@yourdomain.com"
162
-
163
- # Frontend URL for email links (password reset, verification, etc.)
164
- CLIENT_URL="https://yourdomain.com"
165
-
166
- # ===================================================================
167
- # LOGGING
168
- # ===================================================================
169
-
170
- # Production: info or warn (reduces log volume)
171
- # Use warn to reduce costs in high-traffic scenarios
172
- LOG_LEVEL=info
173
-
174
- # ===================================================================
175
- # DATABASE SEEDING
176
- # ===================================================================
177
-
178
- # DO NOT SEED PRODUCTION. The seed script (npm run prisma:seed) creates
179
- # well-known demo accounts (admin@example.com) and hard-refuses to run when
180
- # NODE_ENV=production. These variables exist only to keep .env files in sync
181
- # across environments — leave them commented out in production.
182
- # SEED_ADMIN_PASSWORD=
183
- # SEED_USER_PASSWORD=
184
-
185
- # ===================================================================
186
- # ERROR TRACKING
187
- # ===================================================================
188
-
189
- # Sentry for production error monitoring
190
- # Get your DSN from: https://sentry.io/settings/projects/
191
- SENTRY_DSN="https://YOUR_PUBLIC_KEY@o0.ingest.sentry.io/YOUR_PROJECT_ID"
192
-
193
- # ===================================================================
194
- # PRODUCTION CHECKLIST
195
- # ===================================================================
196
- # [ ] DATABASE_URL uses secure credentials and SSL
197
- # [ ] JWT_SECRET is a strong random string (48+ chars)
198
- # [ ] CORS_ORIGIN is set to your exact frontend URL(s)
199
- # [ ] COOKIE_DOMAIN is set if client and API are on different subdomains
200
- # [ ] REDIS_URL uses authentication if Redis is exposed
201
- # [ ] LOG_LEVEL is set to info or warn
202
- # [ ] SENTRY_DSN is configured for error tracking
203
- # [ ] Database connection pool tested under load
204
- # [ ] All secrets are stored in environment variables, not in code
205
- # [ ] SSL/TLS certificates are configured
206
- # [ ] Firewall rules restrict access to database and Redis
207
- # [ ] In Coolify: NO secrets have "Available at Buildtime" checked
208
- # [ ] 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
+ # Two-tier upload storage. BOTH optionaldefaults are <cwd>/uploads/public and
126
+ # <cwd>/uploads/private. Keep them UNDER /app/uploads (the Coolify volume below)
127
+ # so files survive redeploys.
128
+ # PUBLIC tier: static files at /uploads/ — viewable by ANYONE (e.g. avatars).
129
+ # PRIVATE tier: OUTSIDE the static root; served ONLY via the auth-gated,
130
+ # owner-scoped route GET /api/v1/files/:filename.
131
+ # UPLOAD_PUBLIC_DIR=/app/uploads/public
132
+ # UPLOAD_PRIVATE_DIR=/app/uploads/private
133
+
134
+ # COOLIFY PERSISTENT STORAGE (required for uploads to survive redeployments):
135
+ # Go to your service in Coolify → Storages → Add Volume Mount:
136
+ # Name: uploads (or <project-name>-uploads)
137
+ # Source Path: (leave empty — Coolify manages the Docker volume)
138
+ # Destination Path: /app/uploads
139
+ # Without this, all uploaded files are lost on every redeployment.
140
+
141
+ # ===================================================================
142
+ # JWT AUTHENTICATION
143
+ # ===================================================================
144
+
145
+ # CRITICAL: Generate a cryptographically secure secret
146
+ # Example: openssl rand -base64 48
147
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" this is a secret!
148
+ JWT_SECRET="YOUR_PRODUCTION_JWT_SECRET_MUST_BE_AT_LEAST_32_CHARS_LONG"
149
+
150
+ # Production token expiry (tighter security)
151
+ JWT_ACCESS_EXPIRY="15m"
152
+ JWT_REFRESH_EXPIRY="7d"
153
+
154
+ # Cookie signing secret (separate from JWT for defense-in-depth)
155
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
156
+ COOKIE_SECRET="YOUR_PRODUCTION_COOKIE_SECRET_MUST_BE_AT_LEAST_32_CHARS"
157
+
158
+ # ===================================================================
159
+ # CORS
160
+ # ===================================================================
161
+
162
+ # REQUIRED in production - your frontend domain(s)
163
+ # Multiple origins separated by commas
164
+ CORS_ORIGIN="https://yourdomain.com,https://app.yourdomain.com"
165
+
166
+ # Cookie domain for cross-origin deployments
167
+ # REQUIRED when client and API are on different subdomains:
168
+ # Client: https://app.example.com | API: https://api.example.com
169
+ # → Set COOKIE_DOMAIN=".example.com" (leading dot = all subdomains)
170
+ # This enables cookies to be shared between client and API subdomains.
171
+ # Without it, login will silently fail (server returns 200 but browser drops cookies).
172
+ COOKIE_DOMAIN=".yourdomain.com"
173
+
174
+ # ===================================================================
175
+ # EMAIL (Resend)
176
+ # ===================================================================
177
+
178
+ # Resend API key for transactional emails
179
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
180
+ RESEND_API_KEY="re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
181
+
182
+ # Sender email — must be from a verified domain in Resend
183
+ RESEND_FROM_EMAIL="noreply@yourdomain.com"
184
+
185
+ # Frontend URL for email links (password reset, verification, etc.)
186
+ CLIENT_URL="https://yourdomain.com"
187
+
188
+ # ===================================================================
189
+ # LOGGING
190
+ # ===================================================================
191
+
192
+ # Production: info or warn (reduces log volume)
193
+ # Use warn to reduce costs in high-traffic scenarios
194
+ LOG_LEVEL=info
195
+
196
+ # ===================================================================
197
+ # DATABASE SEEDING
198
+ # ===================================================================
199
+
200
+ # DO NOT SEED PRODUCTION. The seed script (npm run prisma:seed) creates
201
+ # well-known demo accounts (admin@example.com) and hard-refuses to run when
202
+ # NODE_ENV=production. These variables exist only to keep .env files in sync
203
+ # across environments leave them commented out in production.
204
+ # SEED_ADMIN_PASSWORD=
205
+ # SEED_USER_PASSWORD=
206
+
207
+ # ===================================================================
208
+ # ERROR TRACKING
209
+ # ===================================================================
210
+
211
+ # Sentry for production error monitoring (leave unset to disable — fully inert)
212
+ # Get your DSN from: https://sentry.io/settings/projects/
213
+ SENTRY_DSN="https://YOUR_PUBLIC_KEY@o0.ingest.sentry.io/YOUR_PROJECT_ID"
214
+
215
+ # Fraction of transactions sampled for performance tracing (0.0–1.0, default 0.1).
216
+ SENTRY_TRACES_SAMPLE_RATE=0.1
217
+
218
+ # Environment tag on Sentry events (defaults to NODE_ENV when unset).
219
+ SENTRY_ENVIRONMENT=production
220
+
221
+ # ===================================================================
222
+ # PRODUCTION CHECKLIST
223
+ # ===================================================================
224
+ # [ ] DATABASE_URL uses secure credentials and SSL
225
+ # [ ] JWT_SECRET is a strong random string (48+ chars)
226
+ # [ ] CORS_ORIGIN is set to your exact frontend URL(s)
227
+ # [ ] COOKIE_DOMAIN is set if client and API are on different subdomains
228
+ # [ ] REDIS_URL uses authentication if Redis is exposed
229
+ # [ ] LOG_LEVEL is set to info or warn
230
+ # [ ] SENTRY_DSN is configured for error tracking
231
+ # [ ] Database connection pool tested under load
232
+ # [ ] All secrets are stored in environment variables, not in code
233
+ # [ ] SSL/TLS certificates are configured
234
+ # [ ] Firewall rules restrict access to database and Redis
235
+ # [ ] In Coolify: NO secrets have "Available at Buildtime" checked
236
+ # [ ] In Coolify: NODE_ENV does NOT have "Available at Buildtime" checked
@@ -6,7 +6,7 @@
6
6
  # ===================================================================
7
7
  # Stage 1: Dependencies (cached layer)
8
8
  # ===================================================================
9
- FROM node:20-alpine AS dependencies
9
+ FROM node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS dependencies
10
10
 
11
11
  # Install production dependencies only
12
12
  WORKDIR /app
@@ -38,7 +38,7 @@ RUN npm install --no-save --no-audit --no-fund \
38
38
  # ===================================================================
39
39
  # Stage 2: Build (compile TypeScript)
40
40
  # ===================================================================
41
- FROM node:20-alpine AS builder
41
+ FROM node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS builder
42
42
 
43
43
  # Override NODE_ENV to ensure devDependencies are installed.
44
44
  # Coolify may inject NODE_ENV=production as a build arg, which causes npm ci
@@ -70,7 +70,7 @@ RUN npm run build
70
70
  # ===================================================================
71
71
  # Stage 3: Production Runtime
72
72
  # ===================================================================
73
- FROM node:20-alpine AS production
73
+ FROM node:20-alpine@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS production
74
74
 
75
75
  # Install dumb-init for proper signal handling
76
76
  RUN apk add --no-cache dumb-init
@@ -96,8 +96,10 @@ COPY --from=builder --chown=nodejs:nodejs /app/package.json ./package.json
96
96
  # Copy Prisma files (needed for migrations)
97
97
  COPY --from=builder --chown=nodejs:nodejs /app/prisma ./prisma
98
98
 
99
- # Create uploads directory with correct ownership (must be before USER nodejs)
100
- RUN mkdir -p /app/uploads && chown nodejs:nodejs /app/uploads
99
+ # Create BOTH upload tiers with correct ownership (must be before USER nodejs).
100
+ # public/ is served as static files at /uploads/; private/ is served ONLY via
101
+ # the auth-gated route GET /api/v1/files/:filename. See file-storage.service.ts.
102
+ RUN mkdir -p /app/uploads/public /app/uploads/private && chown -R nodejs:nodejs /app/uploads
101
103
 
102
104
  # Switch to non-root user
103
105
  USER nodejs
@@ -17,6 +17,17 @@ services:
17
17
  image: mysql:8.0
18
18
  container_name: {{PROJECT_NAME}}-mysql
19
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
20
31
  ports:
21
32
  - '127.0.0.1:${MYSQL_PORT:-{{MYSQL_PORT}}}:3306'
22
33
  environment:
@@ -57,6 +68,12 @@ services:
57
68
  image: redis:7-alpine
58
69
  container_name: {{PROJECT_NAME}}-redis
59
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', '']
60
77
  ports:
61
78
  - '127.0.0.1:${REDIS_PORT:-{{REDIS_PORT}}}:6379'
62
79
  volumes: