Skip to content

Comments

feat: multi-stage docker image, migrations container, 96.35% smaller#871

Merged
MrgSub merged 7 commits intoMail-0:stagingfrom
adiologydev:feat/docker-improvements
May 16, 2025
Merged

feat: multi-stage docker image, migrations container, 96.35% smaller#871
MrgSub merged 7 commits intoMail-0:stagingfrom
adiologydev:feat/docker-improvements

Conversation

@adiologydev
Copy link
Contributor

@adiologydev adiologydev commented May 4, 2025

Description

This PR introduces an optimised multi-stage Docker image for the main app and an additional image + container to run migrations every time a new version is deployed.

All the images are built as context in the Docker Compose file to avoid requiring external registries.

  • 96.35% Image Size Reduction (5.91GB to 216MB) on the main container.
  • Avoids the need for an env file when deploying via docker since they can now be applied via CLI.
  • Removes exposed ports since they're no longer required besides the main app.
  • Additional migrations image and container to stay up-to-date per deployment.
  • Code cleanup to better organize dockerfiles.
  • Healthchecks on every container.

Type of Change

Please delete options that are not relevant.

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature with breaking changes)
  • 📝 Documentation update
  • 🎨 UI/UX improvement
  • 🔒 Security enhancement
  • ⚡ Performance improvement

Areas Affected

Please check all that apply:

  • Email Integration (Gmail, IMAP, etc.)
  • User Interface/Experience
  • Authentication/Authorization
  • Data Storage/Management
  • API Endpoints
  • Documentation
  • Testing Infrastructure
  • Development Workflow
  • Deployment/Infrastructure

Testing Done

Describe the tests you've done:

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • Cross-browser testing (if UI changes)
  • Mobile responsiveness verified (if UI changes)

Checklist

  • I have read the CONTRIBUTING document
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in complex areas
  • I have updated the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix/feature works
  • All tests pass locally
  • Any dependent changes are merged and published

Screenshots/Recordings

image


By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.

Summary by CodeRabbit

  • New Features

    • Added new services for application runtime and database migrations in the container orchestration setup.
    • Introduced health checks for improved service monitoring and reliability.
    • Added multi-stage Dockerfiles for optimized application and database container builds.
  • Chores

    • Updated ignore rules to exclude unnecessary files from Docker build contexts, reducing image size and build time.
    • Switched to a lighter database image for improved efficiency.
    • Enhanced environment variable management with sensible defaults.

@vercel
Copy link

vercel bot commented May 4, 2025

@adiologydev is attempting to deploy a commit to the Zero Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 4, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This set of changes introduces a new multi-stage Docker build system for a Bun-based Next.js application, replacing the previous Dockerfile with a more modular approach. The new Dockerfiles are organized under docker/app and docker/db for application and database components, respectively. The docker-compose.yaml is extensively revised to add new services for the application and database migrations, update existing services with healthchecks, and improve environment variable handling. The .dockerignore file is added to optimize Docker build contexts by excluding unnecessary files and directories. Additionally, the Next.js configuration in the mail app is updated to conditionally enable standalone output for Docker builds.

Changes

File(s) Change Summary
.dockerignore New file to exclude dependency directories, build outputs, environment files, logs, IDE configs, and other unnecessary files from Docker build contexts.
Dockerfile Removed the original Dockerfile for the Bun-based Next.js application.
apps/mail/next.config.ts Added conditional output: 'standalone' property to Next.js config, enabled when DOCKER_BUILD env variable is set.
docker-compose.yaml Major revision: added zero (app) and migrations services, updated db, valkey, and upstash-proxy services with healthchecks and improved env handling, removed explicit container names and some port mappings, switched db image to Alpine, and declared named volumes.
docker/app/Dockerfile New multi-stage Dockerfile for building and running the Bun-based Next.js app, with separate stages for dependencies, build, and runtime, using non-root user and standalone Next.js output.
docker/db/Dockerfile New multi-stage Dockerfile for database/migrations, separating dependency installation and runtime, and setting up environment variables for database connection.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant Docker as Docker Build System
    participant App as Bun Next.js App
    participant DB as Database (Postgres)
    participant Migrate as Migrations Service

    Dev->>Docker: docker-compose up
    Docker->>DB: Start Postgres container
    Docker->>DB: Run healthcheck (pg_isready)
    Docker->>Migrate: Start migrations container (runs bun run db:migrate)
    Migrate->>DB: Apply migrations
    Docker->>App: Build App image (multi-stage Dockerfile)
    App->>App: Run build (bun run build, output standalone)
    Docker->>App: Start App container
    App->>DB: Connect to database
    App->>App: Expose port 3000, serve requests
Loading

Possibly related PRs

  • add: add dockerfile #591: Replaced the original single Dockerfile with a new multi-stage Dockerfile at docker/app/Dockerfile, directly evolving the previous Docker build setup.

Suggested reviewers

  • MrgSub

Poem

In Docker fields where containers grow,
A rabbit hops where new configs flow.
Compose now sings with healthchecks bright,
And Bun builds apps through day and night.
With .dockerignore, our loads are light—
Standalone, secure, and oh so neat,
This Docker dance is quite a feat!

🐇✨


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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@adiologydev adiologydev marked this pull request as ready for review May 4, 2025 19:06
@MrgSub MrgSub changed the base branch from main to staging May 4, 2025 19:09
Copy link
Contributor

@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

🧹 Nitpick comments (7)
docker/db/Dockerfile (1)

17-34: Well-structured runner stage that follows Docker best practices

The runner stage correctly copies only the necessary files from the deps stage and the source repository. Setting DATABASE_URL as a build argument provides flexibility during image build.

Consider adding a HEALTHCHECK instruction to ensure the migration service can properly report its status:

 WORKDIR /app/packages/db
+
+# Add healthcheck to indicate successful migration run
+HEALTHCHECK --interval=5s --timeout=3s --start-period=10s --retries=3 \
+    CMD [ -f /tmp/migration_success ] || exit 1
apps/mail/next.config.ts (1)

6-6: Critical optimization for Docker builds

The conditional output: 'standalone' setting is key to achieving the 96.35% image size reduction mentioned in the PR objectives. This generates an optimized Next.js output that includes only the necessary runtime files when building for Docker.

Consider adding a comment explaining why this setting is important for Docker builds:

-  output: process.env.DOCKER_BUILD ? 'standalone' : undefined,
+  // Use standalone output for Docker builds to dramatically reduce image size
+  output: process.env.DOCKER_BUILD ? 'standalone' : undefined,
docker/app/Dockerfile (1)

24-76: Effective builder stage with proper environment handling

The builder stage correctly sets up all necessary build arguments and environment variables. Setting DOCKER_BUILD=true on line 69 is particularly important as it activates the standalone output in next.config.ts.

Consider moving the sharp installation to the deps stage to improve build caching:

 # Install turbo globally
 RUN bun install -g turbo
 
 COPY package.json bun.lock turbo.json ./
 RUN mkdir -p apps packages
 COPY apps/*/package.json ./apps/
+COPY apps/mail/package.json ./apps/mail/
 COPY packages/*/package.json ./packages/
 COPY packages/tsconfig/ ./packages/tsconfig/
 
-RUN bun install
+# Install dependencies and sharp for Next.js image optimization
+RUN bun install && cd apps/mail && bun install sharp
docker-compose.yaml (4)

22-26: Ensure migrations complete before starting the app

depends_on without health conditions only waits for container start, not for migrations to finish. The zero service may begin before migrations complete, leading to startup errors. Consider adding a healthcheck to the migrations service or introducing an orchestration step (e.g., an entrypoint script) that waits for migrations to exit successfully before launching the main app.


27-34: Tighten healthcheck configuration

A 90 s interval delays failure detection. Reducing it to around 30 s will catch issues sooner. Additionally, you can explicitly check for a 2xx HTTP status code to make the probe more robust.


57-61: Use dynamic variables in healthcheck

The healthcheck currently hardcodes the database name zerodotemail. If POSTGRES_DB is overridden, the probe will still point to the default. Reference the Compose variables for consistency:

healthcheck:
  test: ['CMD', 'pg_isready', '-U', '${POSTGRES_USER:-postgres}', '-d', '${POSTGRES_DB:-zerodotemail}']

81-87: Reevaluate container_name and env naming in Upstash proxy

  • A hard-coded container_name can collide in multi-env setups; consider removing it to let Compose manage naming automatically.
  • The SRH_TOKEN fallback uses REDIS_TOKEN, which may confuse readers—unify on one variable.
  • Since no ports are published, the curl healthcheck runs inside the container (OK), but if external access is ever needed, add a ports: block.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d73ebfa and 451a337.

📒 Files selected for processing (6)
  • .dockerignore (1 hunks)
  • Dockerfile (0 hunks)
  • apps/mail/next.config.ts (1 hunks)
  • docker-compose.yaml (1 hunks)
  • docker/app/Dockerfile (1 hunks)
  • docker/db/Dockerfile (1 hunks)
💤 Files with no reviewable changes (1)
  • Dockerfile
🔇 Additional comments (4)
.dockerignore (1)

1-48: Well-structured .dockerignore file that properly excludes unnecessary files

This comprehensive .dockerignore file will help reduce Docker build context size and improve build times by excluding unnecessary files like node_modules, build artifacts, and development files. This directly supports the PR's goal of creating smaller, more efficient Docker images.

docker/db/Dockerfile (1)

1-16: Excellent use of multi-stage build with minimal dependencies

The deps stage properly isolates dependency installation and installs only the necessary packages for database operations. Using Alpine-based images aligns with the PR goal of reducing image size.

docker/app/Dockerfile (1)

1-22: Well-structured base and dependencies stages

Good use of Alpine-based images and proper dependency installation strategy. Breaking the build into distinct stages follows Docker best practices and contributes to the smaller image size goal.

docker-compose.yaml (1)

70-74: Approve Redis healthcheck

The valkey service healthcheck correctly verifies container liveness with redis-cli ping and appropriate intervals/retries. This ensures dependent services only start once Redis is ready.

Comment on lines 78 to 138
# ========================================
# Runner Stage: Production Environment
# ========================================
FROM base AS runner
WORKDIR /app

# Define build arguments
ARG NEXT_PUBLIC_APP_URL \
DATABASE_URL \
BETTER_AUTH_SECRET \
BETTER_AUTH_URL \
BETTER_AUTH_TRUSTED_ORIGINS \
GOOGLE_CLIENT_ID \
GOOGLE_CLIENT_SECRET \
REDIS_URL \
REDIS_TOKEN \
RESEND_API_KEY \
OPENAI_API_KEY \
AI_SYSTEM_PROMPT \
GROQ_API_KEY \
GOOGLE_GENERATIVE_AI_API_KEY \
NEXT_TELEMETRY_DISABLED

# Set environment variables for build
ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} \
DATABASE_URL=${DATABASE_URL} \
BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET} \
BETTER_AUTH_URL=${BETTER_AUTH_URL} \
BETTER_AUTH_TRUSTED_ORIGINS=${BETTER_AUTH_TRUSTED_ORIGINS} \
GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} \
GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} \
REDIS_URL=${REDIS_URL} \
REDIS_TOKEN=${REDIS_TOKEN} \
RESEND_API_KEY=${RESEND_API_KEY} \
OPENAI_API_KEY=${OPENAI_API_KEY} \
AI_SYSTEM_PROMPT=${AI_SYSTEM_PROMPT} \
GROQ_API_KEY=${GROQ_API_KEY} \
GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY} \
NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED} \
NODE_ENV=production

RUN addgroup -S -g 1001 bunjs && \
adduser -S -u 1001 nextjs -G bunjs

# Copy public assets
COPY --from=builder --chown=nextjs:bunjs /app/apps/mail/public ./apps/mail/public

# Leverage output traces to reduce image size (standalone output)
COPY --from=builder --chown=nextjs:bunjs /app/apps/mail/.next/standalone ./
COPY --from=builder --chown=nextjs:bunjs /app/apps/mail/.next/static ./apps/mail/.next/static

# Switch to non-root user
USER nextjs

# Set server port and host
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Start the server
CMD ["bun", "apps/mail/server.js"] No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Secure and optimized runner stage

Excellent security practice by creating and using a non-root user (lines 119-130). The use of the Next.js standalone output (lines 126-127) is key to achieving the 96.35% size reduction mentioned in the PR objectives.

Consider using a reusable environment variable setup to avoid duplication between builder and runner stages:

-# Define build arguments
+# Define build arguments with ARG directive
 ARG NEXT_PUBLIC_APP_URL \
     DATABASE_URL \
     BETTER_AUTH_SECRET \
     [...]
     NEXT_TELEMETRY_DISABLED
 
-# Set environment variables for build
-ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} \
-    DATABASE_URL=${DATABASE_URL} \
-    BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET} \
-    [...]
-    NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED} \
-    NODE_ENV=production
+# Use a shell script to set all environment variables from ARGs
+COPY --from=builder /app/docker/app/set-env-vars.sh /set-env-vars.sh
+RUN chmod +x /set-env-vars.sh && \
+    /set-env-vars.sh > /app/.env && \
+    echo "NODE_ENV=production" >> /app/.env
+
+# Load environment variables from file
+ENV $(cat /app/.env | xargs)

This approach requires creating a set-env-vars.sh script in your repository, but would simplify environment variable management and reduce Dockerfile duplication.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 76 to 77
RUN bun run build

Copy link
Contributor

@coderabbitai coderabbitai bot May 4, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add healthcheck for production container

To ensure the container monitoring system can properly report service health status:

 # Start the server
 CMD ["bun", "apps/mail/server.js"]
+
+# Add healthcheck
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
+    CMD wget -q -O - http://localhost:3000/api/health || exit 1

This assumes you have or will implement a health endpoint at /api/health. If you use a different health endpoint path, adjust accordingly.

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We perform the health check in the Docker compose file, thus, this is redundant and not required.

Copy link
Contributor

Choose a reason for hiding this comment

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

@adiologydev You're absolutely right. I see that the healthcheck is indeed already implemented in the Docker Compose file for the zero service. Thank you for clarifying this approach - having the health check in the compose file is a clean way to manage it without duplicating it in the Dockerfile. I appreciate the correction.


✏️ Learnings added
Learnt from: adiologydev
PR: Mail-0/Zero#871
File: docker/app/Dockerfile:76-77
Timestamp: 2025-05-04T19:55:38.542Z
Learning: The Zero project implements container healthchecks in the Docker Compose file rather than in individual Dockerfiles to avoid redundancy.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 36 to 46
migrations:
build:
context: .
dockerfile: docker/db/Dockerfile
args:
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
depends_on:
- db
command: ['bun', 'run', 'db:migrate']
restart: 'no'

Copy link
Contributor

@coderabbitai coderabbitai bot May 4, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize build context for migrations

Using the repo root (.) as the build context bakes unnecessary files into the migrations image and slows builds. Scope the context to the docker/db directory (where your migration Dockerfile and assets live) to speed up builds and shrink context size.

Proposed diff:

   migrations:
     build:
-      context: .
+      context: docker/db
       dockerfile: docker/db/Dockerfile
📝 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
migrations:
build:
context: .
dockerfile: docker/db/Dockerfile
args:
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
depends_on:
- db
command: ['bun', 'run', 'db:migrate']
restart: 'no'
migrations:
build:
context: docker/db
dockerfile: docker/db/Dockerfile
args:
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
depends_on:
- db
command: ['bun', 'run', 'db:migrate']
restart: 'no'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The original context is required since our Docker files access contents throughout the root of the monorepo, changing this would require significant unnecessary manoeuvring.

Copy link
Contributor

Choose a reason for hiding this comment

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

@adiologydev I understand completely. In a monorepo setup, keeping the root context is often necessary to access files across packages. Your approach makes sense given that your Dockerfiles need access to various parts of the project structure.

The good news is you've already addressed the main downside (large context size) by adding a comprehensive .dockerignore file, which is the correct way to optimize build context when you need to maintain access to the full repository structure.


✏️ Learnings added
Learnt from: adiologydev
PR: Mail-0/Zero#871
File: docker-compose.yaml:36-46
Timestamp: 2025-05-04T20:00:52.172Z
Learning: The project uses a monorepo structure where Docker builds require the root context to access files throughout the repository. Changing build contexts for services like migrations would require significant restructuring.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 48 to 53
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: zerodotemail
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-zerodotemail}
Copy link
Contributor

@coderabbitai coderabbitai bot May 4, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix indentation of environment variables

The POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB entries are under environment:, but the PGDATA variable (line 54) is not indented into the same block and will be ignored. It should be nested under environment:.

Proposed diff:

   db:
     image: postgres:17-alpine
     restart: unless-stopped
     environment:
-      POSTGRES_USER: ${POSTGRES_USER:-postgres}
-      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
-      POSTGRES_DB: ${POSTGRES_DB:-zerodotemail}
-    PGDATA: /var/lib/postgresql/data/pgdata
+      POSTGRES_USER: ${POSTGRES_USER:-postgres}
+      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
+      POSTGRES_DB: ${POSTGRES_DB:-zerodotemail}
+      PGDATA: /var/lib/postgresql/data/pgdata
📝 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
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: zerodotemail
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-zerodotemail}
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-zerodotemail}
PGDATA: /var/lib/postgresql/data/pgdata

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an incorrect suggestion. It's properly indented.

Copy link
Contributor

Choose a reason for hiding this comment

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

@adiologydev You're absolutely right, and I apologize for my incorrect suggestion. Looking at the full file content again, I can see that PGDATA is indeed properly indented under the environment: block. Thank you for the correction!


✏️ Learnings added
Learnt from: adiologydev
PR: Mail-0/Zero#871
File: docker-compose.yaml:48-53
Timestamp: 2025-05-04T19:53:46.722Z
Learning: In docker-compose.yaml, the PGDATA environment variable for PostgreSQL containers should be properly indented under the environment block. The current indentation in the project is correct.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 2 to 21
zero:
build:
context: .
dockerfile: docker/app/Dockerfile
args:
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-my-better-auth-secret}
BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
REDIS_URL: http://upstash-proxy:80
REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
RESEND_API_KEY: ${RESEND_API_KEY}
OPENAI_API_KEY: ${OPENAI_API_KEY}
AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
GROQ_API_KEY: ${GROQ_API_KEY}
GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
Copy link
Contributor

@coderabbitai coderabbitai bot May 4, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid baking secrets into the build image

Build args are stored in Docker image metadata and can leak sensitive information (API keys, auth secrets, etc.). It's safer to pass these values at container runtime via the environment section rather than as args. Also, consider narrowing the build context to docker/app to reduce build payload.

Proposed diff:

 services:
   zero:
     build:
-      context: .
+      context: docker/app
       dockerfile: docker/app/Dockerfile
-      args:
-        NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
-        DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
-        BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-my-better-auth-secret}
-        BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
-        BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
-        GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
-        GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
-        REDIS_URL: http://upstash-proxy:80
-        REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
-        RESEND_API_KEY: ${RESEND_API_KEY}
-        OPENAI_API_KEY: ${OPENAI_API_KEY}
-        AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
-        GROQ_API_KEY: ${GROQ_API_KEY}
-        GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}
-        NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
+      args:
+        NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
+    environment:
+      NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
+      DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
+      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
+      BETTER_AUTH_URL: ${BETTER_AUTH_URL}
+      BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS}
+      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
+      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
+      REDIS_URL: http://upstash-proxy:80
+      REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
+      RESEND_API_KEY: ${RESEND_API_KEY}
+      OPENAI_API_KEY: ${OPENAI_API_KEY}
+      AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
+      GROQ_API_KEY: ${GROQ_API_KEY}
+      GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although we could let some runtime variables out of the arguments, nextjs still require some during static generation at build time. How would you like to address this?

Copy link
Contributor

Choose a reason for hiding this comment

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

@adiologydev You're absolutely right. Next.js has specific requirements for environment variables during build time, especially for static site generation. Let's take a hybrid approach:

services:
  zero:
    build:
      context: .
      dockerfile: docker/app/Dockerfile
      args:
        # Keep variables needed during build time
        NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
        DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
        BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
        BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
        NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
        # Remove sensitive values from build args
-       BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-my-better-auth-secret}
-       GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
-       GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
-       REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
-       RESEND_API_KEY: ${RESEND_API_KEY}
-       OPENAI_API_KEY: ${OPENAI_API_KEY}
-       AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
-       GROQ_API_KEY: ${GROQ_API_KEY}
-       GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}
        REDIS_URL: http://upstash-proxy:80
    # Add all variables as runtime environment variables
    environment:
      NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
      DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-my-better-auth-secret}
      BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
      BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
      REDIS_URL: http://upstash-proxy:80
      REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
      RESEND_API_KEY: ${RESEND_API_KEY}
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
      GROQ_API_KEY: ${GROQ_API_KEY}
      GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}
      NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}

This approach:

  1. Keeps essential build-time variables as build args (NEXT_PUBLIC_* and those needed for SSG)
  2. Moves sensitive credentials to runtime-only environment variables
  3. Ensures all variables are available at runtime through the environment section

The Dockerfile would need to be updated to use these environment variables at runtime instead of build args for the sensitive values.

Would this compromise work for your Next.js build requirements while improving security?


✏️ Learnings added
Learnt from: adiologydev
PR: Mail-0/Zero#871
File: docker-compose.yaml:2-21
Timestamp: 2025-05-04T23:13:26.797Z
Learning: Next.js requires certain environment variables during static site generation at build time, particularly those with the NEXT_PUBLIC_ prefix. When using Docker, these should be passed as build args, while sensitive values like API keys and secrets should ideally only be passed at runtime as environment variables.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And what if we want to completely disable SSG project wide so that everything's a runtime variable? Any suggestions on that?

Copy link
Contributor

Choose a reason for hiding this comment

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

@adiologydev Yes, it's possible to disable SSG project-wide and use only runtime variables. Here's how you can approach it:

  1. Force Server-Side Rendering (SSR) for all pages:
    In your next.config.ts, add:

    const nextConfig = {
      // Existing config...
      output: 'standalone',
      runtime: 'nodejs',
      experimental: {
        // Existing experimental features...
        serverComponentsExternalPackages: [], // If using App Router
      }
    };
  2. Convert all static pages to dynamic:
    For Pages Router, use getServerSideProps in all pages
    For App Router, set export const dynamic = 'force-dynamic' in layout/page files

  3. Update Docker setup:

    services:
      zero:
        build:
          context: .
          dockerfile: docker/app/Dockerfile
          args:
    -       NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
    -       DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
    -       BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-my-better-auth-secret}
    -       BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
    -       BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
    -       GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
    -       GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
    -       REDIS_URL: http://upstash-proxy:80
    -       REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
    -       RESEND_API_KEY: ${RESEND_API_KEY}
    -       OPENAI_API_KEY: ${OPENAI_API_KEY}
    -       AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
    -       GROQ_API_KEY: ${GROQ_API_KEY}
    -       GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}
    -       NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
    +       DOCKER_BUILD: "true"
    +       NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
        environment:
    +     NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
    +     DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-zerodotemail}
    +     BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-my-better-auth-secret}
    +     BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
    +     BETTER_AUTH_TRUSTED_ORIGINS: ${BETTER_AUTH_TRUSTED_ORIGINS:-http://localhost:3000}
    +     GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
    +     GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
    +     REDIS_URL: http://upstash-proxy:80
    +     REDIS_TOKEN: ${REDIS_TOKEN:-upstash-local-token}
    +     RESEND_API_KEY: ${RESEND_API_KEY}
    +     OPENAI_API_KEY: ${OPENAI_API_KEY}
    +     AI_SYSTEM_PROMPT: ${AI_SYSTEM_PROMPT}
    +     GROQ_API_KEY: ${GROQ_API_KEY}
    +     GOOGLE_GENERATIVE_AI_API_KEY: ${GOOGLE_GENERATIVE_AI_API_KEY}
    +     NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}

Trade-offs to consider:

  • ✅ Better security (no secrets in build)
  • ✅ Simpler Docker setup with all variables at runtime
  • ❌ Potentially slower initial page loads
  • ❌ Higher server resource usage

The standalone output option still provides optimal container size while enabling runtime environment variables to work properly.


✏️ Learnings added
Learnt from: adiologydev
PR: Mail-0/Zero#871
File: docker-compose.yaml:2-21
Timestamp: 2025-05-04T23:21:21.374Z
Learning: To completely disable SSG (Static Site Generation) in Next.js projects for Docker deployments, use 'force-dynamic' for App Router or getServerSideProps for Pages Router in all pages, and set output: 'standalone' in next.config.ts. This allows all environment variables to be passed at runtime instead of build time, improving security for sensitive values.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: adiologydev
PR: Mail-0/Zero#871
File: docker-compose.yaml:2-21
Timestamp: 2025-05-04T23:13:26.797Z
Learning: Next.js requires certain environment variables during static site generation at build time, particularly those with the NEXT_PUBLIC_ prefix. When using Docker, these should be passed as build args, while sensitive values like API keys and secrets should ideally only be passed at runtime as environment variables.

@BlankParticle
Copy link
Contributor

Can you separate out the deploy docker-compose from the db and redis,
While I am developing locally I don't need to build the app.
You can do a docker-compose.yaml for services used in dev and a docker-compose.deploy.yaml for deployment

@ShadowArcanist
Copy link

Hey @adiologydev I just tested it!

The Dockerfile is working fine but compose is not.

The app services depends on upstash-proxy, and the upstash-proxy is failing the healthcheck due to curl not present in the upstash-proxy container.

Could you please update the CMD on upstash-proxy service on the compose (or do something to make the health check pass)?

image

image

@adiologydev adiologydev force-pushed the feat/docker-improvements branch from 18e046c to 44b0bdb Compare May 6, 2025 23:30
@adiologydev
Copy link
Contributor Author

All resolved, ready for merge. Had an old upstream merge before causing conflict.

@adiologydev
Copy link
Contributor Author

Pushing an update with seperate backend soon

@MrgSub MrgSub merged commit 09807dc into Mail-0:staging May 16, 2025
1 of 2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request May 28, 2025
9 tasks
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.

4 participants