feat: add frontend Dockerfile and update docker-compose files for fro…#20
feat: add frontend Dockerfile and update docker-compose files for fro…#20chakrihacker wants to merge 17 commits into
Conversation
Reviewer's GuideThis PR introduces a Bun-based Docker build and runtime pipeline for the frontend (including a multi-stage Dockerfile, a custom Bun server script, and docker-compose updates), enhances package scripts/dependencies, and includes a minor UI container refactor and API Dockerfile caching tweak. Flow diagram for Bun-based frontend server request handlingflowchart TD
A["Incoming HTTP Request"]
B["Bun.serve() routes"]
C["/_expo/static/* or /assets/*"]
D["Check file existence in ./dist/client"]
E["Serve file if exists"]
F["404 Not Found if missing"]
G["/* (other routes)"]
H["createRequestHandler (SSR)"]
I["/canvaskit.wasm"]
J["Serve canvaskit.wasm if exists"]
K["404 Not Found if missing"]
A --> B
B --> C
C --> D
D -- exists --> E
D -- missing --> F
B --> G
G --> H
B --> I
I --> J
I --> K
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a Bun-based frontend service (server, Dockerfile, .dockerignore, package changes, env updates), registers frontend in dev/prod compose files, adjusts API Dockerfile to stop copying the full Changes
Sequence Diagram(s)sequenceDiagram
participant B as Browser
participant S as Bun Frontend Server (port 4000)
participant C as Dist Client (static files)
participant E as Expo Handler (dist/server)
B->>S: HTTP request
alt Static asset (/ _expo/static/*, /assets/*, /canvaskit.wasm)
S->>C: Check file existence (Bun.file(...).exists)
alt exists
S-->>B: 200 Serve file
else
S-->>B: 404 Not found
end
else Dynamic route (other paths)
S->>E: Forward request to Expo handler
E-->>S: Handler response
S-->>B: Proxy response
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (1)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 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 |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes and they look great!
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `apps/frontend/server.ts:10` </location>
<code_context>
+const server = Bun.serve({
+ port: 4000,
+ routes: {
+ "/_expo/static/*": async (request) => {
+ const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
+ const exists = await file.exists();
</code_context>
<issue_to_address>
Static file serving does not set Content-Type headers.
Set the Content-Type header according to the file extension to ensure correct browser handling.
Suggested implementation:
```typescript
"/_expo/static/*": async (request) => {
const url = new URL(request.url);
const filePath = `./dist/client/${url.pathname}`;
const file = Bun.file(filePath);
const exists = await file.exists();
// Determine content type
const ext = path.extname(filePath).toLowerCase();
const contentTypeMap: Record<string, string> = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".map": "application/json",
};
const contentType = contentTypeMap[ext] || "application/octet-stream";
return exists
? new Response(file, { headers: { "Content-Type": contentType } })
: new Response("Not found", { status: 404 });
```
```typescript
"/assets/*": async (request) => {
const url = new URL(request.url);
const filePath = `./dist/client/${url.pathname}`;
const file = Bun.file(filePath);
const exists = await file.exists();
// Determine content type
const ext = path.extname(filePath).toLowerCase();
const contentTypeMap: Record<string, string> = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".map": "application/json",
};
const contentType = contentTypeMap[ext] || "application/octet-stream";
return exists
? new Response(file, { headers: { "Content-Type": contentType } })
: new Response("Not found", { status: 404 });
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const server = Bun.serve({ | ||
| port: 4000, | ||
| routes: { | ||
| "/_expo/static/*": async (request) => { |
There was a problem hiding this comment.
suggestion (bug_risk): Static file serving does not set Content-Type headers.
Set the Content-Type header according to the file extension to ensure correct browser handling.
Suggested implementation:
"/_expo/static/*": async (request) => {
const url = new URL(request.url);
const filePath = `./dist/client/${url.pathname}`;
const file = Bun.file(filePath);
const exists = await file.exists();
// Determine content type
const ext = path.extname(filePath).toLowerCase();
const contentTypeMap: Record<string, string> = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".map": "application/json",
};
const contentType = contentTypeMap[ext] || "application/octet-stream";
return exists
? new Response(file, { headers: { "Content-Type": contentType } })
: new Response("Not found", { status: 404 }); "/assets/*": async (request) => {
const url = new URL(request.url);
const filePath = `./dist/client/${url.pathname}`;
const file = Bun.file(filePath);
const exists = await file.exists();
// Determine content type
const ext = path.extname(filePath).toLowerCase();
const contentTypeMap: Record<string, string> = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".map": "application/json",
};
const contentType = contentTypeMap[ext] || "application/octet-stream";
return exists
? new Response(file, { headers: { "Content-Type": contentType } })
: new Response("Not found", { status: 404 });There was a problem hiding this comment.
Review by Korbit AI
Korbit automatically attempts to detect when you fix issues in new commits.
| Category | Issue | Status |
|---|---|---|
| Path Traversal Vulnerability in File Serving ▹ view | ||
| Inconsistent Error Messages ▹ view | ||
| Hard-coded Port Number ▹ view | ||
| Violates DRY principle in route handlers ▹ view | ||
| Violation of Single Responsibility Principle ▹ view |
Files scanned
| File Path | Reviewed |
|---|---|
| apps/frontend/server.ts | ✅ |
| apps/frontend/src/containers/auth/sign-up.container.tsx | ✅ |
Explore our documentation to understand the languages and file types we support and the files we ignore.
Check out our docs on how you can make Korbit work best for you and your team.
| port: 4000, | ||
| routes: { | ||
| "/_expo/static/*": async (request) => { | ||
| const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`); |
There was a problem hiding this comment.
Path Traversal Vulnerability in File Serving 
Tell me more
What is the issue?
The code doesn't sanitize or validate the pathname from the URL, which could lead to path traversal vulnerabilities.
Why this matters
Malicious users could potentially access files outside the intended directory by using '../' in the URL path.
Suggested change ∙ Feature Preview
Implement path validation and normalization before serving files:
const pathname = new URL(request.url).pathname;
const normalizedPath = path.normalize(pathname).replace(/^\.+/, '');
const file = Bun.file(`./dist/client/${normalizedPath}`);Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
| const exists = await file.exists(); | ||
| return exists | ||
| ? new Response(file) | ||
| : new Response("Not found", { status: 404 }); |
There was a problem hiding this comment.
Inconsistent Error Messages 
Tell me more
What is the issue?
Inconsistent error messages across different route handlers ('Not found' vs 'Not found here') could confuse client applications.
Why this matters
Different error messages for the same error condition (404) can make error handling more difficult for clients and complicate debugging.
Suggested change ∙ Feature Preview
Standardize error messages across all routes:
const NOT_FOUND_RESPONSE = new Response("Not found", { status: 404 });
// Then use it consistently in all routes
return exists ? new Response(file) : NOT_FOUND_RESPONSE;Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
| const server = Bun.serve({ | ||
| port: 4000, |
There was a problem hiding this comment.
Hard-coded Port Number 
Tell me more
What is the issue?
Hard-coded port number reduces configurability and readability.
Why this matters
Using magic numbers makes it harder to understand the significance of the value and complicates configuration changes across different environments.
Suggested change ∙ Feature Preview
Extract the port into a named constant or environment variable:
const PORT = process.env.PORT || 4000;
const server = Bun.serve({
port: PORT,Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
| "/_expo/static/*": async (request) => { | ||
| const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`); | ||
| const exists = await file.exists(); | ||
| return exists | ||
| ? new Response(file) | ||
| : new Response("Not found", { status: 404 }); | ||
| }, | ||
| "/assets/*": async (request) => { | ||
| const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`); | ||
| const exists = await file.exists(); | ||
| return exists | ||
| ? new Response(file) | ||
| : new Response("Not found", { status: 404 }); | ||
| }, |
There was a problem hiding this comment.
Violates DRY principle in route handlers 
Tell me more
What is the issue?
Duplicate file serving logic appears in multiple route handlers
Why this matters
Code duplication increases maintenance burden and risk of inconsistencies when changes are needed
Suggested change ∙ Feature Preview
Extract the common file serving logic into a reusable function:
const serveStaticFile = async (request: Request) => {
const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found", { status: 404 });
};
// Then use in routes:
routes: {
"/_expo/static/*": serveStaticFile,
"/assets/*": serveStaticFile,
// ...
}Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
| routes: { | ||
| "/_expo/static/*": async (request) => { | ||
| const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`); | ||
| const exists = await file.exists(); | ||
| return exists | ||
| ? new Response(file) | ||
| : new Response("Not found", { status: 404 }); | ||
| }, |
There was a problem hiding this comment.
Violation of Single Responsibility Principle 
Tell me more
What is the issue?
Static file serving configuration is mixed with server setup code
Why this matters
Mixing concerns makes the code harder to maintain and configure, particularly when static file serving rules need to be modified
Suggested change ∙ Feature Preview
Extract static file serving configuration into a separate module:
// staticFileConfig.ts
export const staticFileRoutes = {
"/_expo/static/*": serveStaticFile,
"/assets/*": serveStaticFile,
"/canvaskit.wasm": serveWasmFile
};
// server.ts
import { staticFileRoutes } from './staticFileConfig';
const server = Bun.serve({
port: 4000,
routes: {
...staticFileRoutes,
"/*": async (request) => await handler(request),
},
});Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/Dockerfile (1)
51-51: Do not bake .env into the runtime image (secrets leakage).Copying .env* into the final image risks leaking credentials. Pass env via orchestrator (compose/secrets) at runtime.
-# Copy .env file to runtime image for Prisma -COPY --from=build /app/apps/api/.env* ./Compose example (prod):
services: api: environment: - DATABASE_URL=${DATABASE_URL} # or: # env_file: # - ../apps/api/.env.production # secrets: # - db_url
🧹 Nitpick comments (9)
apps/frontend/package.json (2)
21-21: Pin @expo/server or document update policy.Caret range can pull breaking changes. Consider exact pin or Renovate rule to avoid surprise deploys.
-"@expo/server": "^0.6.3" +"@expo/server": "0.6.3"
12-13: Pin Bun version in Dockerfile and verify required build flags
ReplaceFROM oven/bun:latestin apps/frontend/Dockerfile with a specific Bun release (e.g. oven/bun:v1.x.y) to ensure reproducible builds, and confirm that the pinned version supports--compile,--minify-whitespace,--minify-syntaxand--target bun.apps/api/Dockerfile (1)
37-44: Avoid passing build flags through a package script unless guaranteed.bun run --filter @universal/api build --compile ... assumes the 'build' script forwards unknown args. Consider inlining bun build here or assert the script does forward.
-RUN bun run --filter @universal/api build \ - --compile \ - --minify-whitespace \ - --minify-syntax \ - --target bun \ - --outfile server \ - ./src/index.ts +# Directly invoke bun build to avoid script arg-forwarding pitfalls +RUN bun build \ + --compile \ + --minify-whitespace \ + --minify-syntax \ + --target bun \ + --outfile apps/api/server \ + apps/api/src/index.tsapps/frontend/src/containers/auth/sign-up.container.tsx (1)
13-21: Extract inline param type and narrow 'field' to known keys.Inline types hurt reuse; also allow only valid form keys to prevent typos at call sites.
- const handleFormDataChange = ({ - field, - value, - }: { - field: string; - value: string; - }) => { + type FormField = keyof typeof formData; + type UpdateFormDataPayload = { field: FormField; value: string }; + const handleFormDataChange = ({ field, value }: UpdateFormDataPayload) => { signUpModel$.updateFormData({ field, value }); };apps/frontend/server.ts (3)
10-23: Serve static assets with caching and correct content types; also de-duplicate route logic.Adds long-lived cache headers for hashed asset paths and reduces duplication.
Apply this diff:
- "/_expo/static/*": async (request) => { - const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`); - const exists = await file.exists(); - return exists - ? new Response(file) - : new Response("Not found", { status: 404 }); - }, - "/assets/*": async (request) => { - const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`); - const exists = await file.exists(); - return exists - ? new Response(file) - : new Response("Not found", { status: 404 }); - }, + "/_expo/static/*": async (request) => { + const pathname = new URL(request.url).pathname; + const file = Bun.file(`./dist/client/${pathname}`); + const exists = await file.exists(); + return exists + ? new Response(file, { + headers: { + // Bun infers content-type in many cases, but being explicit avoids edge cases. + "Content-Type": file.type || "application/octet-stream", + "Cache-Control": "public, max-age=31536000, immutable", + }, + }) + : new Response("Not found", { status: 404 }); + }, + "/assets/*": async (request) => { + const pathname = new URL(request.url).pathname; + const file = Bun.file(`./dist/client/${pathname}`); + const exists = await file.exists(); + return exists + ? new Response(file, { + headers: { + "Content-Type": file.type || "application/octet-stream", + "Cache-Control": "public, max-age=31536000, immutable", + }, + }) + : new Response("Not found", { status: 404 }); + },
7-9: Bind to PORT from env and 0.0.0.0 for container compatibility.Prevents port conflicts and ensures the service is reachable behind Docker/Cloud.
-const server = Bun.serve({ - port: 4000, +const server = Bun.serve({ + hostname: "0.0.0.0", + port: Number(Bun.env.PORT ?? 4000),
35-36: Avoid console in committed code; gate or remove the log.Project guideline: “Don't use console or debugger in committed code.”
-// biome-ignore lint/suspicious/noConsole: logging the server URL -console.log(`Server listening on ${server.url}`); +if (Bun.env.NODE_ENV !== "test") { + // eslint-disable-next-line no-console + console.log(`Server listening on ${server.url}`); +}docker/docker-compose.prod.yml (1)
18-25: Consider healthchecks and startup ordering for SSR/API dependencies.If SSR calls the API during request handling, add depends_on with health conditions to avoid boot-time 5xx.
frontend: build: context: .. dockerfile: ./apps/frontend/Dockerfile ports: - "4000:4000" env_file: - - ../apps/frontend/.env + - ../apps/frontend/.env + depends_on: + api: + condition: service_healthyEnsure api defines a healthcheck.
docker/docker-compose.dev.yml (1)
43-46: Dev container may not hot-reload frontend code.You mount source, but the container runs a compiled binary from the image. Without a watch/build step, code changes won’t reflect.
Options:
- Switch CMD in dev to run Bun in watch mode and serve from source (different Dockerfile target).
- Or add a bind for dist and trigger a build step on file changes.
Would you like a minimal dev-target Dockerfile and compose override?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
apps/api/Dockerfile(1 hunks)apps/frontend/.env(1 hunks)apps/frontend/.env.example(1 hunks)apps/frontend/Dockerfile(1 hunks)apps/frontend/package.json(1 hunks)apps/frontend/server.ts(1 hunks)apps/frontend/src/containers/auth/sign-up.container.tsx(1 hunks)docker/docker-compose.dev.yml(1 hunks)docker/docker-compose.prod.yml(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use object destructuring for method parameters (wrap multiple parameters in an object; define shapes with TypeScript interfaces)
Use authClient.useSession() for session state
Avoid React hooks; use Legend State observables directly (exception: Better Auth hooks)
Import UI components via path aliases (e.g., @/src/components/ui/)
Prefer union string/number literals over TypeScript enums
Use export type and import type for types
Don't use non-null assertions (!) or implicit any
Avoid TypeScript namespaces; prefer ESM modules
Files:
apps/frontend/src/containers/auth/sign-up.container.tsxapps/frontend/server.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use Platform.OS and process.env.EXPO_OS for conditional platform logic
Files:
apps/frontend/src/containers/auth/sign-up.container.tsxapps/frontend/server.ts
**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.tsx: Use React Native primitives (View, Text, etc.) instead of HTML elements
Use Tailwind responsive prefixes (sm:, md:, lg:) and NativeWind responsive utilities
Use web: prefix for web-only styles; use conditional rendering for native-only components
Use IconSymbol to map SF Symbols on iOS to Material Icons on other platforms
Web a11y: ensure buttons have type, anchors are valid, SVGs/iframes have title, html has lang, inputs have valid autocomplete, ARIA roles/props/values are valid
Files:
apps/frontend/src/containers/auth/sign-up.container.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx,js,jsx}: Don't use the arguments object
Use for...of instead of Array.forEach
Use arrow functions instead of function expressions
Use Date.now() instead of new Date().getTime()
Use .flatMap() instead of map().flat() when possible
Prefer regex literals over RegExp constructor when possible
Use concise optional chaining instead of chained logical expressions
Always provide radix to parseInt()
Don't use var; use const/let and prefer const when single assignment
Use template literals over string concatenation
Use strict equality (===/!==)
Don't use console or debugger in committed code
Prevent import cycles and avoid namespace imports
Don't hardcode secrets (API keys/tokens) in source
Don't have unused vars, params, imports, labels, or private members
Avoid await inside loops; ensure Promise-like statements are handled
Disallow bitwise operators and comma operator
Ensure switch statements are exhaustive; default clause comes last; no fallthrough or duplicate cases
Use object spread over Object.assign when creating new objects
Prefer Array.isArray over instanceof Array; use at() instead of integer index access where appropriate
No unreachable code, duplicate conditions, or assignments in conditionals
Always use radix with parseInt; prefer Number.isNaN/Number.isFinite over globals
Disallow using global eval, with, var, and unsafe negation
Ensure typeof comparisons use valid strings; getters return values; constructors don’t return values
Files:
apps/frontend/src/containers/auth/sign-up.container.tsxapps/frontend/server.ts
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{tsx,jsx}: Provide key props in lists; avoid array index as key
Don't define React components inside other components
Don't use event handlers on non-interactive elements; make interactive roles focusable
Don't use both children and dangerouslySetInnerHTML; avoid dangerous JSX props
Use <>...</> instead of ...
Files:
apps/frontend/src/containers/auth/sign-up.container.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-03T13:07:10.615Z
Learnt from: CR
PR: fyndx/universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-03T13:07:10.615Z
Learning: Applies to src/containers/**/*.container.tsx : Create containers in src/containers/ mirroring Expo Router paths (e.g., admin/add-user.container.tsx)
Applied to files:
apps/frontend/src/containers/auth/sign-up.container.tsx
🧬 Code graph analysis (1)
apps/frontend/src/containers/auth/sign-up.container.tsx (1)
apps/frontend/src/app/(public)/auth/sign-in.tsx (3)
SignIn(21-180)ctx(41-52)
🔇 Additional comments (4)
apps/api/Dockerfile (1)
45-57: Confirm CA certificates and timezone data in all distroless/base Docker images
Distroless/base omits CA bundles and tzdata by default. For apps/api/Dockerfile (line 45), apps/workers/Dockerfile (line 33), and apps/frontend/Dockerfile (line 26), verify the presence of/etc/ssl/certsand timezone data—or switch togcr.io/distroless/base-debian(which includes certs and tzdata) or layer them in manually—to ensure Prisma/HTTP TLS and correct timezone handling.apps/frontend/src/containers/auth/sign-up.container.tsx (2)
9-11: State access via Legend observables looks good.
43-58: UI flow/render conditions unchanged — LGTM.Prop order/formatting only; behavior consistent.
docker/docker-compose.dev.yml (1)
34-41: Env wiring looks correct for dev.Pathing matches the repository layout and aligns with the prod fix above.
| @@ -1,2 +1,2 @@ | |||
| EXPO_PUBLIC_API_URL=https://universal-starter.onrender.com | |||
| EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io | |||
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Prod compose will point frontend to staging API — config mismatch.
If docker-compose.prod.yml loads this file, the public app will call the staging API. Split envs and reference the right one per compose file.
Run to confirm compose env_file linkage:
Recommended changes:
-EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io
+EXPO_PUBLIC_API_URL=http://localhost:3000And create per-env files:
+apps/frontend/.env.development
+EXPO_PUBLIC_API_URL=http://localhost:3000
+
+apps/frontend/.env.production
+EXPO_PUBLIC_API_URL=https://api.yourdomain.comThen in compose:
- dev: env_file: ../apps/frontend/.env.development
- prod: env_file: ../apps/frontend/.env.production
🏁 Script executed:
#!/bin/bash
# Verify which env files prod/dev compose reference
rg -nP 'env_file\s*:\s*(.+)$' -C2 docker/docker-compose.*.ymlLength of output: 96
🏁 Script executed:
#!/bin/bash
# Search for any .env references in compose files
rg -nP '\.env' -n -C2 docker/docker-compose.*.yml
# Search for explicit EXPO_PUBLIC_API_URL usage in compose files
rg -nP 'EXPO_PUBLIC_API_URL' -n -C2 docker/docker-compose.*.ymlLength of output: 1701
Split frontend .env for production vs. development
Both docker-compose.dev.yml and docker-compose.prod.yml load the same apps/frontend/.env, which currently points at the staging API (EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io), so production will call staging.
- Create
apps/frontend/.env.developmentwithEXPO_PUBLIC_API_URL=http://localhost:3000 - Create
apps/frontend/.env.productionwithEXPO_PUBLIC_API_URL=https://api.yourdomain.com - Update
docker-compose.dev.ymltoenv_file: ../apps/frontend/.env.development - Update
docker-compose.prod.ymltoenv_file: ./apps/frontend/.env.production
🤖 Prompt for AI Agents
In apps/frontend/.env around line 1, the current single .env points to the
staging API causing production to call staging; create two environment files
apps/frontend/.env.development (set EXPO_PUBLIC_API_URL=http://localhost:3000)
and apps/frontend/.env.production (set
EXPO_PUBLIC_API_URL=https://api.yourdomain.com), then update
docker-compose.dev.yml to reference env_file: ../apps/frontend/.env.development
and docker-compose.prod.yml to reference env_file:
./apps/frontend/.env.production so dev and prod load the correct environment
variables.
| @@ -1,2 +1,2 @@ | |||
| EXPO_PUBLIC_API_URL=https://universal-starter.onrender.com | |||
| EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Don't default examples to a staging endpoint; use placeholders per env.
Hardcoding a staging URL in the example can leak intent and get copied to prod. Prefer a neutral default with comments for env-specific overrides.
-EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io
+EXPO_PUBLIC_API_URL=http://localhost:3000
+# For production: https://api.yourdomain.com📝 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.
| EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io | |
| EXPO_PUBLIC_API_URL=http://localhost:3000 | |
| # For production: https://api.yourdomain.com |
🤖 Prompt for AI Agents
In apps/frontend/.env.example around lines 1 to 1, the example currently
hardcodes a staging API URL; replace this with a neutral placeholder and a short
comment indicating environment-specific overrides. Change the value to something
like EXPO_PUBLIC_API_URL=https://your-api.example.com (or
EXPO_PUBLIC_API_URL=${API_URL}) and add a one-line comment above explaining to
set the real endpoint per environment (development/staging/production) rather
than copying a staging URL.
| FROM gcr.io/distroless/base | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY --from=build /app/apps/frontend/dist ./dist | ||
| COPY --from=build /app/apps/frontend/server ./server | ||
|
|
||
| ENV NODE_ENV=production | ||
|
|
||
| CMD [ "./server" ] | ||
|
|
||
| EXPOSE 4000 No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
Harden the final image: run as nonroot and ensure certs are present.
Distroless defaults to root. Run as nonroot and pre-own the files. CA certs are typically present in distroless base, but if SSR makes outbound HTTPS calls, verify at runtime.
FROM gcr.io/distroless/base
WORKDIR /app
-COPY --from=build /app/apps/frontend/dist ./dist
-COPY --from=build /app/apps/frontend/server ./server
+COPY --from=build /app/apps/frontend/dist ./dist
+COPY --from=build /app/apps/frontend/server ./server
+USER nonroot:nonroot
ENV NODE_ENV=production
CMD [ "./server" ]
EXPOSE 4000After building, verify HTTPS works from the container (e.g., fetch https://example.com from SSR) to confirm CA certs availability.
Enforce non-root execution and correct file ownership
- Switch to the non-root variant (or add
USER nonroot:nonroot) and ensure/appcontent is owned by that user, for example:(stackoverflow.com)FROM gcr.io/distroless/base:nonroot WORKDIR /app COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/dist ./dist COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/server ./server ENV NODE_ENV=production CMD ["./server"] EXPOSE 4000
- The base image already includes CA certificates; verify outbound HTTPS (e.g.
fetch https://example.com) from the container at runtime. (github.com)
| import { createRequestHandler } from "@expo/server"; | ||
| import Bun from "bun"; | ||
|
|
||
| const handler = createRequestHandler(path.join(import.meta.dir, "dist/server")); |
There was a problem hiding this comment.
SSR handler path will break inside the container (import.meta.dir points to build-time path). Use a cwd-based path.
At runtime, the compiled binary runs from /app and dist is copied to /app/dist. Using import.meta.dir risks resolving to /app/apps/frontend (or a build-time path) that doesn’t exist in the final image.
Apply this diff:
-const handler = createRequestHandler(path.join(import.meta.dir, "dist/server"));
+const handler = createRequestHandler(path.resolve("dist/server"));📝 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.
| const handler = createRequestHandler(path.join(import.meta.dir, "dist/server")); | |
| const handler = createRequestHandler(path.resolve("dist/server")); |
🤖 Prompt for AI Agents
In apps/frontend/server.ts around line 5, the SSR handler currently uses
import.meta.dir which resolves a build-time path and will break inside the
container; change it to resolve the runtime working directory (e.g., use
process.cwd() or path.resolve(process.cwd(), 'dist', 'server')) so the handler
points to /app/dist/server at runtime; update the createRequestHandler call to
join/resolve against process.cwd() instead of import.meta.dir.
| "/*": async (request) => await handler(request), | ||
| "/canvaskit.wasm": async (_request) => { | ||
| const file = Bun.file("./dist/client/canvaskit.wasm"); | ||
| const exists = await file.exists(); | ||
| return exists | ||
| ? new Response(file) | ||
| : new Response("Not found here", { status: 404 }); | ||
| }, |
There was a problem hiding this comment.
Wildcard route likely shadows /canvaskit.wasm. Put the wasm route before "/*".
With a catch-all "/*" in routing, the /canvaskit.wasm handler may never be reached.
Apply this diff to reorder:
- "/*": async (request) => await handler(request),
- "/canvaskit.wasm": async (_request) => {
+ "/canvaskit.wasm": async (_request) => {
const file = Bun.file("./dist/client/canvaskit.wasm");
const exists = await file.exists();
return exists
- ? new Response(file)
+ ? new Response(file, {
+ headers: {
+ "Content-Type": "application/wasm",
+ "Cache-Control": "public, max-age=31536000, immutable",
+ },
+ })
: new Response("Not found here", { status: 404 });
},
+ "/*": async (request) => await handler(request),📝 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.
| "/*": async (request) => await handler(request), | |
| "/canvaskit.wasm": async (_request) => { | |
| const file = Bun.file("./dist/client/canvaskit.wasm"); | |
| const exists = await file.exists(); | |
| return exists | |
| ? new Response(file) | |
| : new Response("Not found here", { status: 404 }); | |
| }, | |
| "/canvaskit.wasm": async (_request) => { | |
| const file = Bun.file("./dist/client/canvaskit.wasm"); | |
| const exists = await file.exists(); | |
| return exists | |
| ? new Response(file, { | |
| headers: { | |
| "Content-Type": "application/wasm", | |
| "Cache-Control": "public, max-age=31536000, immutable", | |
| }, | |
| }) | |
| : new Response("Not found here", { status: 404 }); | |
| }, | |
| "/*": async (request) => await handler(request), |
🤖 Prompt for AI Agents
In apps/frontend/server.ts around lines 24 to 31, the catch-all "/*" route
currently appears before the specific "/canvaskit.wasm" route causing the wasm
handler to be shadowed; reorder the routes so the "/canvaskit.wasm" entry comes
before the "/*" entry (move the /canvaskit.wasm block above the wildcard) so the
specific path is matched first and the catch-all remains last.
| env_file: | ||
| - ./apps/frontend/.env |
There was a problem hiding this comment.
Broken env_file path; should be relative to the compose file directory.
dev uses ../apps/frontend/.env; prod should match. ./apps/frontend/.env will resolve to docker/apps/frontend/.env and fail.
- env_file:
- - ./apps/frontend/.env
+ env_file:
+ - ../apps/frontend/.env📝 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.
| env_file: | |
| - ./apps/frontend/.env | |
| env_file: | |
| - ../apps/frontend/.env |
🤖 Prompt for AI Agents
In docker/docker-compose.prod.yml around lines 24 to 25, the env_file path is
incorrect (./apps/frontend/.env) because env_file is resolved relative to the
compose file directory; change it to ../apps/frontend/.env to match dev and
ensure the .env file is found (update any other env_file entries similarly if
they use ./ instead of ../).
… frontend service
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
.github/workflows/docker-build.yml (5)
83-83: Tag format change removes hyphen — verify downstream expectations.
Switching from branch- to branch can break scripts or dashboards that parse tag patterns and reduces readability.Apply to restore the hyphen if not intentional:
- type=sha,prefix={{branch}} + type=sha,prefix={{branch}}-
122-122: Workers image tags: keep hyphen for consistency/back-compat.
Mirror the API decision to avoid mixed formats across services.- type=sha,prefix={{branch}} + type=sha,prefix={{branch}}-
134-171: New build-frontend job — solid. Two quick improvements: fork-safety and build cache.
- Push on forked PRs will fail due to lack of package write perms. Gate push/login accordingly.
- Add BuildKit cache to speed rebuilds.
- name: Log in to Container Registry - uses: docker/login-action@v3 + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend tags: | type=ref,event=branch type=ref,event=pr - type=sha,prefix={{branch}} + type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Frontend image uses: docker/build-push-action@v5 with: context: . file: ./apps/frontend/Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + cache-from: type=gha + cache-to: type=gha,mode=max tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
146-152: Optional: minimize noisy failures on forked PRs.
Adding the same fork-guard to API/Workers login steps will keep all jobs consistent.
153-163: Action versions: bump to latest stable releases
• docker/build-push-action → v6.15.0
• docker/metadata-action → v5.7.0
• docker/login-action → v3.3.0
• dorny/paths-filter → v3.0.2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
.github/workflows/docker-build.yml(7 hunks)apps/frontend/Dockerfile(1 hunks)apps/frontend/package.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/frontend/Dockerfile
- apps/frontend/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-frontend
🔇 Additional comments (4)
.github/workflows/docker-build.yml (4)
10-10: Triggers: frontend included on push — looks good.
Keeps CI scoped and avoids unnecessary builds.
19-19: Triggers: frontend included on PRs — good coverage.
Consistent with push trigger.
33-33: paths-filter output wired for frontend — LGTM.
Enables clean job gating.
51-55: Frontend filter definition — sensible paths.
Including packages/** is appropriate for a shared monorepo.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/frontend/Dockerfile (1)
26-35: Harden runtime: run as non-root and pre-own files in distrolessRepeat of prior note—still applicable. Switch to nonroot variant and set ownership to avoid permission issues and improve security posture.
-FROM gcr.io/distroless/base +FROM gcr.io/distroless/base:nonroot WORKDIR /app -COPY --from=build /app/apps/frontend/dist ./dist -COPY --from=build /app/apps/frontend/server ./server +COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/dist ./dist +COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/server ./server ENV NODE_ENV=production -CMD [ "./server" ] +USER nonroot:nonroot +CMD ["./server"]
🧹 Nitpick comments (2)
apps/frontend/Dockerfile (2)
3-6: Clean up unused ENV or wire it through
ENV ENVIRONMENT=${ENVIRONMENT}is unused; either drop it or setNODE_ENVfrom it for clarity.-ARG ENVIRONMENT=production -ENV ENVIRONMENT=${ENVIRONMENT} -# ENV NODE_ENV=${ENVIRONMENT} +ARG ENVIRONMENT=production +ENV NODE_ENV=${ENVIRONMENT}
1-1: Pin builder and runtime images for reproducibility
oven/bun:latestand distroless:latestdrift. Pin tags (and ideally digests) to prevent surprise breakages in CI.Example:
-FROM oven/bun:latest AS base +FROM oven/bun:1.1.XX-debian AS base -... -FROM gcr.io/distroless/base:nonroot +FROM gcr.io/distroless/base:nonroot@sha256:<digest>Also applies to: 26-26
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
apps/frontend/Dockerfile(1 hunks)apps/frontend/package.json(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/frontend/package.json
🧰 Additional context used
🪛 GitHub Actions: Docker Build
apps/frontend/Dockerfile
[error] 22-22: Command 'cd apps/frontend && bunx expo export --platform web' failed with exit code 7 during image build.
🔇 Additional comments (2)
apps/frontend/Dockerfile (2)
37-37: Ports match EXPOSE 4000
server.tsexplicitly listens on port 4000, matching the Dockerfile.
9-9: No changes needed;bun.lockis the correct canonical Bun lockfile
The repository contains abun.lock(text‐based lockfile introduced in Bun v1.1.39 and default since v1.2) and copying it in the Dockerfile is appropriate.Likely an incorrect or invalid review comment.
| RUN bun install --ignore-scripts | ||
| # Create web build | ||
| RUN cd apps/frontend && bunx expo export --platform web | ||
| # Create server build | ||
| RUN cd apps/frontend && bun run expo:server |
There was a problem hiding this comment.
Unblock failing build: don’t ignore postinstalls and avoid bunx for Expo; ensure Node is available
The pipeline fails at Line 22. Likely causes: (a) --ignore-scripts skips needed postinstalls, (b) Expo CLI expects Node (bunx can be incompatible). Install Node, run full install, and invoke the package script.
WORKDIR /app
-RUN bun install --ignore-scripts
-# Create web build
-RUN cd apps/frontend && bunx expo export --platform web
-# Create server build
-RUN cd apps/frontend && bun run expo:server
+RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*
+RUN bun install --frozen-lockfile
+# Create web build (use workspace script so it resolves local config/deps)
+RUN cd apps/frontend && bun run export:web
+# Create server build
+RUN cd apps/frontend && bun run expo:server📝 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.
| RUN bun install --ignore-scripts | |
| # Create web build | |
| RUN cd apps/frontend && bunx expo export --platform web | |
| # Create server build | |
| RUN cd apps/frontend && bun run expo:server | |
| WORKDIR /app | |
| RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/* | |
| RUN bun install --frozen-lockfile | |
| # Create web build (use workspace script so it resolves local config/deps) | |
| RUN cd apps/frontend && bun run export:web | |
| # Create server build | |
| RUN cd apps/frontend && bun run expo:server |
🧰 Tools
🪛 GitHub Actions: Docker Build
[error] 22-22: Command 'cd apps/frontend && bunx expo export --platform web' failed with exit code 7 during image build.
🤖 Prompt for AI Agents
In apps/frontend/Dockerfile around lines 20-24, the build is failing because
--ignore-scripts prevents required postinstall steps and bunx can be
incompatible with Expo (Node not ensured); remove the --ignore-scripts flag so
postinstall scripts run, ensure a Node runtime is installed in the image before
running installs (install nodejs/node and npm or nvm in the Dockerfile), and
replace bunx expo invocations with the project's package script executed via the
package manager (e.g., run the repository's expo export/server scripts with bun
run or npm run from apps/frontend) so Expo runs under a proper Node environment.
…plify copy commands
….json and bun.lock
…po/metro-runtime from devDependencies
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/frontend/Dockerfile (2)
19-22: Build step likely fails: run the script viabun run(and avoidbunx).Use the workspace script so Expo runs under your project config.
bunx expo …is brittle here and was the previous failure point.-RUN cd apps/frontend && \ - bunx expo export --platform web +RUN cd apps/frontend && \ + bun run export:web RUN cd apps/frontend && \ bun run export:server
25-35: Harden the runtime image: run as non-root and ensure ownership.Distroless defaults to root. Switch to the nonroot variant and copy with ownership to avoid permission surprises at runtime.
-FROM gcr.io/distroless/base +FROM gcr.io/distroless/base:nonroot WORKDIR /app -COPY --from=base ./apps/frontend/dist . -COPY --from=base ./apps/frontend/server . +COPY --from=base --chown=nonroot:nonroot /app/apps/frontend/dist ./dist +COPY --from=base --chown=nonroot:nonroot /app/apps/frontend/server ./server ENV NODE_ENV=production +USER nonroot:nonroot CMD [ "./server" ] EXPOSE 4000
🧹 Nitpick comments (5)
apps/frontend/Dockerfile (2)
16-18: Avoid redundant per-package install.Installing in the workspace root is sufficient; a second install in apps/frontend is unnecessary and can create drift.
-RUN cd apps/frontend && \ - bun install +# Root install above covers all workspaces; no per-package install needed
5-7: ENVIRONMENT only exists in the build stage.If the binary reads ENV at runtime, propagate ENVIRONMENT in the final stage too.
ARG ENVIRONMENT=production ENV ENVIRONMENT=${ENVIRONMENT} # ENV NODE_ENV=production +... +FROM gcr.io/distroless/base:nonroot +ARG ENVIRONMENT=production +ENV ENVIRONMENT=${ENVIRONMENT}.github/workflows/docker-build.yml (3)
146-157: Trim redundant pre-build steps; let Docker build do the builds.These steps rebuild web/server outside the image and add CI time without affecting the Docker build result. Prefer relying on the Dockerfile or move these into a separate “verify” job.
- - name: Set up Bun - uses: oven-sh/setup-bun@v2 - - - name: Install dependencies - run: bun install - - - name: Build Web - run: cd apps/frontend && bun run export:web - - - name: Build Server - run: cd apps/frontend && bun run export:server + # Optional: keep a separate verify job if you want fast feedback outside Docker
169-175: Tag format change: confirm consumers expect no dash between branch and SHA.You changed to
type=sha,prefix={{branch}}. Ensure any tooling parsing tags doesn’t rely on the old hyphen.
176-183: Speed up Docker builds with BuildKit cache and pass build args.Push/pull cache shortens monorepo builds; propagate ENVIRONMENT if you intend to vary builds.
- name: Build and push Frontend image uses: docker/build-push-action@v5 with: context: . file: ./apps/frontend/Dockerfile push: true + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:buildcache + cache-to: type=registry,mode=max,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:buildcache + build-args: | + ENVIRONMENT=production tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.github/workflows/docker-build.yml(7 hunks)apps/frontend/.dockerignore(1 hunks)apps/frontend/Dockerfile(1 hunks)apps/frontend/package.json(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/frontend/.dockerignore
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/frontend/package.json
🔇 Additional comments (1)
.github/workflows/docker-build.yml (1)
10-10: LGTM: frontend path filters and outputs are wired correctly.Also applies to: 19-19, 33-34, 51-55
| COPY . . | ||
|
|
||
| # Install dependencies (this layer will be cached if package files don't change) | ||
| RUN bun install --ignore-scripts | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fix caching and postinstall behavior: don’t copy the whole context before install; drop --ignore-scripts.
Copy only manifests first to leverage Docker layer cache, then run a strict install. --ignore-scripts will likely break Expo-related postinstalls.
WORKDIR /app
-# Copy package files first for better caching
-COPY . .
-
-# Install dependencies (this layer will be cached if package files don't change)
-RUN bun install --ignore-scripts
+# Copy manifests for better caching
+COPY package.json bun.lockb ./
+COPY packages/*/package.json packages/*/
+COPY apps/*/package.json apps/*/
+# Install deps (fail if lock diverges)
+RUN bun install --frozen-lockfile
+
+# Now bring in the rest
+COPY . .📝 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 . . | |
| # Install dependencies (this layer will be cached if package files don't change) | |
| RUN bun install --ignore-scripts | |
| WORKDIR /app | |
| # Copy manifests for better caching | |
| COPY package.json bun.lockb ./ | |
| COPY packages/*/package.json packages/*/ | |
| COPY apps/*/package.json apps/*/ | |
| # Install deps (fail if lock diverges) | |
| RUN bun install --frozen-lockfile | |
| # Now bring in the rest of the application | |
| COPY . . |
🤖 Prompt for AI Agents
In apps/frontend/Dockerfile around lines 11 to 15, the Dockerfile currently
copies the entire build context before running bun install and uses
--ignore-scripts; change this to copy only dependency manifests (e.g.,
package.json, package-lock.json, bun.lockb, yarn.lock, and any Expo-specific
config files) into the image, run bun install without --ignore-scripts, then
copy the rest of the repo. Remove the --ignore-scripts flag so postinstall
scripts run (needed for Expo), and ensure manifests are copied first to get
proper layer caching before the full COPY . .
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
apps/frontend/app.json(1 hunks)apps/frontend/package.json(5 hunks)
🔇 Additional comments (3)
apps/frontend/package.json (3)
12-14: Remove build artifact name clarity nit. Theexport:serverscript produces a binary namedserver, which exactly matches both theCOPY --from=base ./apps/frontend/server .andCMD ["./server"]in apps/frontend/Dockerfile.Likely an incorrect or invalid review comment.
2-2: Ensure complete renaming of@universal/appto@universal/super-app
I didn’t find any remaining@universal/appreferences in your package.json files, but please search all source (e.g.rg -nP "['\]@universal/app['`]"`) and update any import/require statements, Docker contexts, CI configs or workspace entries still pointing to the old name.
21-21: Confirmed @expo/server version compatibility: ^0.6.3 aligns with Expo SDK 53 and expo-router 5.1.x; no changes needed.
| "expo-video", | ||
| "expo-secure-store" | ||
| ], |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
SecureStore plugin: verify web behavior and usage guards.
Adding "expo-secure-store" is fine for native. On web builds it can be unsecure or limited; ensure calls are guarded (e.g., Platform.OS !== 'web') or handled with a fallback.
If you’re targeting web (per web.output=server), confirm SDK 53’s web support/limitations for expo-secure-store and whether additional configuration is required:
🌐 Web query:
Does Expo SDK 53 support expo-secure-store on Web, and what are its limitations or recommended fallbacks?
💡 Result:
Short answer: No — in Expo SDK 53 expo-secure-store does not support web. The SDK 53 docs state isAvailableAsync resolves true on Android and iOS only (web is not supported). [1]
Key limitations to note:
- No web implementation in SDK 53 (mobile-only). [1]
- Stored value size limit ≈ 2048 bytes. [2]
- requireAuthentication has platform caveats (not supported in Expo Go when FaceID/biometrics need extra plist key). [2]
- Data is not guaranteed to survive app uninstall; keys tied to biometrics can be invalidated if biometrics change. [1][2]
Recommended fallbacks on web:
- Use a web storage solution (localStorage, sessionStorage, or IndexedDB) or @react-native-async-storage/async-storage when running on web, optionally combined with a JS-level encryption layer if you need confidentiality. Many users switch to AsyncStorage/localStorage on Platform.OS === 'web' and use SecureStore only on mobile. [3][4]
Sources:
- Expo SDK 53 SecureStore docs. [1]
- Expo SecureStore general docs (size limit, platform notes). [2]
- Community example recommending AsyncStorage fallback for web. [3]
References:
[1] Expo SDK 53 SecureStore docs — docs.expo.dev/versions/v53.0.0/sdk/securestore/
[2] Expo SecureStore (general) — docs.expo.dev/sdk/securestore/
[3] StackOverflow: use AsyncStorage on web when SecureStore not available.
Guard SecureStore on Web and Implement Fallback
expo-secure-store isn’t supported on Web in Expo SDK 53; wrap all SecureStore calls in Platform.OS !== 'web' and fallback to AsyncStorage/localStorage (optionally with JS-level encryption) when running on Web.
| "export:web": "bunx expo export --platform web", | ||
| "export:server": "bun build --compile --minify-whitespace --minify-syntax --target bun --outfile server ./server.ts", | ||
| "lint": "expo lint", |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Bun scripts added: pin Bun and ensure postinstall compatibility.
Since you rely on bun/bunx, declare the runtime to avoid accidental npm/yarn environments; also revisit postinstall which currently uses npx and can fail under Bun-only builds.
Add an engines/packageManager hint (example outside this hunk):
{
"engines": { "bun": ">=1.1.20" },
"packageManager": "bun@1.1.20"
}Consider switching postinstall to bunx (or make it conditional):
- "postinstall": "npx setup-skia-web"
+ "postinstall": "bunx setup-skia-web"🤖 Prompt for AI Agents
In apps/frontend/package.json around lines 12 to 14, the project uses bun/bunx
scripts but does not declare Bun as the runtime or pin the packageManager, and
the postinstall still uses npx which can fail in Bun-only environments; add an
"engines" entry (e.g. "bun": ">=1.1.20") and a "packageManager" field (e.g.
"bun@1.1.20") to the package.json root, and update the postinstall script to use
bunx (or make it conditional: detect bun and fall back to npx/yarn/npm) so
postinstall works reliably under Bun while keeping compatibility with other
environments.
| "class-variance-authority": "^0.7.1", | ||
| "clsx": "^2.1.1", | ||
| "expo": "53.0.20", | ||
| "expo": "53.0.22", |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
SDK patch bumps: confirm alignment across Expo ecosystem.
Upgrading expo and related packages is good; ensure all versions match the supported set for SDK 53 (React/React Native, Expo modules).
If you can, run expo-doctor locally. Otherwise, confirm via docs:
Also applies to: 58-61, 64-64
🌐 Web query:
For Expo SDK 53, what are the recommended versions of react, react-native, expo-router, expo-system-ui, and expo-secure-store?
💡 Result:
Recommended versions for Expo SDK 53
- react: 19.0.0. [1][2]
- react-native: 0.79 (0.79.x). [1][2]
- expo-router: 5.x (use the 5.1.x series / sdk-52+ 5.x releases). [3]
- expo-system-ui: ~5.0.x (5.0.* series). [4]
- expo-secure-store: ~14.2.3 (bundled ~14.2.3 for SDK 53). [5]
Sources: Expo SDK version matrix & SDK 53 release notes; expo-router npm; expo-system-ui npm; expo-secure-store docs.
[1] https://docs.expo.dev/versions/latest/ — SDK/React/React Native mapping.
[2] https://expo.dev/changelog/sdk-53 — SDK 53 release notes.
[3] https://www.npmjs.com/package/expo-router — expo-router versions/tags.
[4] https://www.npmjs.com/package/expo-system-ui — expo-system-ui versions.
[5] https://docs.expo.dev/versions/latest/sdk/securestore — bundled version info.
Align peer dependency versions with Expo SDK 53
In apps/frontend/package.json, update dependencies to the SDK-53–bundled versions:
• react → 19.0.0 (docs.expo.dev)
• react-native → 0.79 (docs.expo.dev)
• expo-router → ^5.1.4 (npmjs.com)
• expo-system-ui → ~5.0.11 (docs.expo.dev)
• expo-secure-store → ~14.2.3 (docs.expo.dev)
Ensure all other expo-* modules use versions bundled with SDK 53.
🤖 Prompt for AI Agents
In apps/frontend/package.json around line 51, update dependency versions to
match Expo SDK 53: set "react" to "19.0.0", "react-native" to "0.79",
"expo-router" to "^5.1.4", "expo-system-ui" to "~5.0.11", and
"expo-secure-store" to "~14.2.3"; also audit and align all other "expo-*" module
versions to the SDK‑53 bundled versions per the Expo 53 docs so the package.json
reflects the exact SDK‑53 compatible dependency set.
…ersion in package.json and bun.lock
…ntend service
Note
I'm currently writing a description for your pull request. I should be done shortly (<1 minute). Please don't edit the description field until I'm finished, or we may overwrite each other. If I find nothing to write about, I'll delete this message.
Summary by Sourcery
Containerize the frontend application using Bun and expo/server, add a server entrypoint to serve builds, update Docker Compose configurations to include the frontend service, and introduce build scripts along with minor code and Dockerfile cleanup
New Features:
Enhancements:
Deployment: