Skip to content

feat: added dockerfile to web app and fixed vite config to use --host#34

Merged
BuckyMcYolo merged 2 commits intomainfrom
dev
Apr 2, 2026
Merged

feat: added dockerfile to web app and fixed vite config to use --host#34
BuckyMcYolo merged 2 commits intomainfrom
dev

Conversation

@BuckyMcYolo
Copy link
Copy Markdown
Owner

@BuckyMcYolo BuckyMcYolo commented Apr 1, 2026

Overview

This PR adds Docker containerization for the web application with Nginx runtime configuration, updates the Vite preview server to bind to all interfaces, integrates the web service into docker-compose, and adjusts an environment variable default.

Changes

Docker Support for Web App

  • apps/web/Dockerfile: Added multi-stage Docker build using node:22-slim (build) and nginx:alpine (runtime). Build stage enables Corepack, activates pnpm@9.0.0, installs dependencies with pnpm install --frozen-lockfile, builds @repo/api, @repo/api-client, and the web package. Declares build args NEXT_PUBLIC_API_URL, NEXT_PUBLIC_REALTIME_URL, and NEXT_PUBLIC_MAX_FILE_UPLOAD_SIZE and exports them as environment variables for the build. Runtime stage copies apps/web/dist into Nginx web root, replaces Nginx config with apps/web/nginx.conf, exposes port 3000, and runs Nginx in the foreground.

  • apps/web/nginx.conf: Added Nginx configuration serving the SPA on port 3000, enabling SPA fallback (try_files $uri $uri/ /index.html) and long-lived caching for static assets (expires 1y; Cache-Control: public, immutable).

Docker Compose Integration

  • docker-compose.yml: Added web service that builds from apps/web/Dockerfile with build args NEXT_PUBLIC_API_URL and NEXT_PUBLIC_REALTIME_URL (defaults to localhost values), exposes port 3000:3000, restarts unless-stopped, and depends on the api service (condition: service_started).

Configuration Updates

  • apps/web/package.json: Modified the start script to include --host for Vite preview (vite preview --port 3000 --host) so the preview server binds to all interfaces (allowing containerized access).

  • packages/env/src/server.ts: Changed the SELF_HOSTED environment variable default from true to false in the Zod schema (SELF_HOSTED: z.coerce.boolean().default(false)).

Issues & Concerns

  • Efficiency: The Dockerfile builds @repo/api and @repo/api-client during the web build. If those builds are unnecessary for producing the web static assets, this increases build time and image complexity and could be optimized.
  • Breaking change risk: Changing SELF_HOSTED default from true to false alters runtime defaults and may affect existing deployments that rely on the previous default. Consider documenting this change and its implications or require explicit env override.

Confidence Score: 4/5

The changes are coherent and correctly implement Docker build args and runtime configuration. Main concerns are build efficiency (unnecessary package builds) and the behavioral change to the SELF_HOSTED default which should be communicated to operators.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 1, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 295af4d1-0f54-474f-9b88-adb46f81b0f7

📥 Commits

Reviewing files that changed from the base of the PR and between 20bec18 and fabde11.

📒 Files selected for processing (1)
  • apps/web/Dockerfile

📝 Walkthrough

Walkthrough

Adds containerization for the web app: new multi-stage apps/web/Dockerfile, Nginx runtime config, docker-compose web service, Vite preview binding change, and an environment default change for SELF_HOSTED from true to false.

Changes

Cohort / File(s) Summary
Web Docker + Runtime
apps/web/Dockerfile, apps/web/nginx.conf
Added a multi-stage Dockerfile (Node 22 + pnpm build stage, nginx:alpine runtime) and an Nginx config that serves the SPA with fallback routing and long-lived cache headers for static assets.
Compose service
docker-compose.yml
Added web service building from apps/web/Dockerfile with build args (NEXT_PUBLIC_API_URL, NEXT_PUBLIC_REALTIME_URL), exposes 3000:3000, restarts unless-stopped, and depends on api (service_started).
Dev preview binding
apps/web/package.json
Updated start script to run vite preview --port 3000 --host so the preview server binds to all interfaces.
Env defaults
packages/env/src/server.ts
Changed SELF_HOSTED Zod schema default from true to false.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hopped into builds with a cheerful wink,

Layers and caches stacked in a blink,
Nginx guards the routes with a nimble paw,
Compose ties services together — hurrah!
Now web hops from dev straight to deploy 🌷

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding a Dockerfile to the web app and modifying the vite config to use --host. However, it omits other significant changes like the nginx.conf, docker-compose.yml updates, and environment variable default modification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/Dockerfile`:
- Around line 16-20: The Dockerfile currently uses FROM nginx:alpine and runs
nginx as root because there is no USER instruction; update the final stage to
drop privileges by adding a USER directive (e.g., set the container to USER
nginx or another unprivileged user) so the nginx process started by CMD
["nginx", "-g", "daemon off;"] runs non-root; ensure this change is compatible
with the copied site files (COPY --from=build /app/apps/web/dist
/usr/share/nginx/html) and nginx.conf and that file permissions allow the chosen
user to read the files and bind to EXPOSE 3000.
- Around line 6-14: The Dockerfile's build stage doesn't declare or expose
NEXT_PUBLIC_API_URL and NEXT_PUBLIC_REALTIME_URL, so the web build won't see
compose values; add ARG NEXT_PUBLIC_API_URL and ARG NEXT_PUBLIC_REALTIME_URL in
the build stage and then set ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL and
ENV NEXT_PUBLIC_REALTIME_URL=$NEXT_PUBLIC_REALTIME_URL before the RUN pnpm
--filter web build command so the pnpm --filter web build (and any build-time
tools) pick up those values from the build args.

In `@docker-compose.yml`:
- Around line 118-120: The build args NEXT_PUBLIC_API_URL and
NEXT_PUBLIC_REALTIME_URL in docker-compose.yml currently default to
http://localhost:8080 and http://localhost:8000 which will be baked into the SPA
and break for remote clients; change the defaults so the image is built with
either explicit public origins or same-origin/relative URLs (for example use a
relative root '/' or leave no default so the CI/CD/deployment injects the
correct public origin) and ensure NEXT_PUBLIC_API_URL and
NEXT_PUBLIC_REALTIME_URL are set to the real public hostnames in your deployment
pipeline or compose override rather than localhost.

In `@packages/env/src/server.ts`:
- Line 36: The SELF_HOSTED config default was changed to false but the shipped
config/docs expect true; update the SELF_HOSTED Zod declaration (SELF_HOSTED:
z.coerce.boolean().default(...)) to .default(true) so the runtime default
matches .env.example and docker-compose behavior, and verify any related docs or
docker-compose environment omissions remain consistent with SELF_HOSTED=true.
- Line 36: The SELF_HOSTED schema entry currently uses z.coerce.boolean() which
interprets any non-empty string as true; update the SELF_HOSTED validator in
packages/env/src/server.ts to parse the raw string explicitly (e.g., accept
"true" or "1" → true, "false" or "0" → false, case-insensitive) and then coerce
that into a boolean with a default of false so that values like "false" and "0"
resolve correctly; reference the SELF_HOSTED schema key and ensure the validator
returns a boolean type and still applies the default(false) behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c6e00306-5a5b-4e86-ab06-fb5403d286eb

📥 Commits

Reviewing files that changed from the base of the PR and between 337c98a and 20bec18.

📒 Files selected for processing (5)
  • apps/web/Dockerfile
  • apps/web/nginx.conf
  • apps/web/package.json
  • docker-compose.yml
  • packages/env/src/server.ts

Comment thread apps/web/Dockerfile
Comment thread apps/web/Dockerfile
Comment on lines +16 to +20
FROM nginx:alpine
COPY --from=build /app/apps/web/dist /usr/share/nginx/html
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
rg -n '^(FROM|USER|EXPOSE|CMD)\b' apps/web/Dockerfile

Repository: BuckyMcYolo/townhall

Length of output: 166


Add a USER instruction to run the Nginx container as an unprivileged user.

The final stage starts with FROM nginx:alpine (line 16) but never includes a USER instruction, so the container runs as root. Since the Nginx service listens on port 3000 (not a privileged port < 1024), there is no need to retain elevated privileges. Add an explicit USER directive to drop to a non-root user (e.g., USER nginx), or switch to an unprivileged Nginx base image variant.

🧰 Tools
🪛 Checkov (3.2.510)

[low] 1-20: Ensure that HEALTHCHECK instructions have been added to container images

(CKV_DOCKER_2)


[low] 1-20: Ensure that a user for the container has been created

(CKV_DOCKER_3)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/Dockerfile` around lines 16 - 20, The Dockerfile currently uses FROM
nginx:alpine and runs nginx as root because there is no USER instruction; update
the final stage to drop privileges by adding a USER directive (e.g., set the
container to USER nginx or another unprivileged user) so the nginx process
started by CMD ["nginx", "-g", "daemon off;"] runs non-root; ensure this change
is compatible with the copied site files (COPY --from=build /app/apps/web/dist
/usr/share/nginx/html) and nginx.conf and that file permissions allow the chosen
user to read the files and bind to EXPOSE 3000.

Comment thread docker-compose.yml
Comment on lines +118 to +120
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8080}
NEXT_PUBLIC_REALTIME_URL: ${NEXT_PUBLIC_REALTIME_URL:-http://localhost:8000}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't ship localhost as the public API/realtime origin.

These values are compiled into the SPA during the image build. Once the app is opened from another machine, http://localhost:8080 and http://localhost:8000 point to the visitor's machine, not the Docker host, so API and realtime calls fail. Use explicit public origins for deployment, or same-origin/relative URLs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 118 - 120, The build args
NEXT_PUBLIC_API_URL and NEXT_PUBLIC_REALTIME_URL in docker-compose.yml currently
default to http://localhost:8080 and http://localhost:8000 which will be baked
into the SPA and break for remote clients; change the defaults so the image is
built with either explicit public origins or same-origin/relative URLs (for
example use a relative root '/' or leave no default so the CI/CD/deployment
injects the correct public origin) and ensure NEXT_PUBLIC_API_URL and
NEXT_PUBLIC_REALTIME_URL are set to the real public hostnames in your deployment
pipeline or compose override rather than localhost.

REDIS_URL: z.string().url(),
BETTER_AUTH_SECRET: z.string().min(1),
SELF_HOSTED: z.coerce.boolean().default(true),
SELF_HOSTED: z.coerce.boolean().default(false),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the new SELF_HOSTED default in sync with the shipped config.

.env.example still documents SELF_HOSTED=true, and docker-compose.yml in this PR still leaves the variable unset. That means compose deployments now silently get false while the documented default remains true.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/env/src/server.ts` at line 36, The SELF_HOSTED config default was
changed to false but the shipped config/docs expect true; update the SELF_HOSTED
Zod declaration (SELF_HOSTED: z.coerce.boolean().default(...)) to .default(true)
so the runtime default matches .env.example and docker-compose behavior, and
verify any related docs or docker-compose environment omissions remain
consistent with SELF_HOSTED=true.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e

echo "Current parser:"
sed -n '34,37p' packages/env/src/server.ts

echo
echo "JavaScript truthiness used by z.coerce.boolean():"
node - <<'NODE'
for (const value of ["true", "false", "1", "0", "", undefined]) {
  console.log(`${String(value)} -> ${Boolean(value)}`)
}
NODE

Repository: BuckyMcYolo/townhall

Length of output: 409


Replace z.coerce.boolean() with explicit string parsing to avoid truthiness bugs.

process.env is string-based. z.coerce.boolean() applies JavaScript truthiness, so SELF_HOSTED="false" and SELF_HOSTED="0" both parse as true (non-empty strings are truthy). Users setting the variable explicitly to false will get the opposite behavior.

🔧 Proposed fix
-  SELF_HOSTED: z.coerce.boolean().default(false),
+  SELF_HOSTED: z
+    .preprocess((value) => {
+      if (typeof value === "boolean") return value
+      if (typeof value === "string") {
+        const normalized = value.trim().toLowerCase()
+        if (normalized === "true") return true
+        if (normalized === "false") return false
+      }
+      return value
+    }, z.boolean())
+    .default(false),
📝 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.

Suggested change
SELF_HOSTED: z.coerce.boolean().default(false),
SELF_HOSTED: z
.preprocess((value) => {
if (typeof value === "boolean") return value
if (typeof value === "string") {
const normalized = value.trim().toLowerCase()
if (normalized === "true") return true
if (normalized === "false") return false
}
return value
}, z.boolean())
.default(false),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/env/src/server.ts` at line 36, The SELF_HOSTED schema entry
currently uses z.coerce.boolean() which interprets any non-empty string as true;
update the SELF_HOSTED validator in packages/env/src/server.ts to parse the raw
string explicitly (e.g., accept "true" or "1" → true, "false" or "0" → false,
case-insensitive) and then coerce that into a boolean with a default of false so
that values like "false" and "0" resolve correctly; reference the SELF_HOSTED
schema key and ensure the validator returns a boolean type and still applies the
default(false) behavior.

@BuckyMcYolo BuckyMcYolo merged commit cb6152f into main Apr 2, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant