From abb13510582291bce4e24b82799fef43dc93f7b1 Mon Sep 17 00:00:00 2001 From: vishalmakwana111 Date: Mon, 21 Apr 2025 09:56:45 +0530 Subject: [PATCH 1/3] feat: Add Dockerfile and CI workflow for web app builds Adds a production Dockerfile (`docker/Dockerfile.prod`) for the `apps/web` Next.js application and a GitHub Actions workflow (`.github/workflows/publish-docker.yml`) to automatically build and publish images to GHCR. Includes dummy environment variables in the Dockerfile and modifications to Sanity components to ensure successful builds. Configures CI triggers and tagging for `main` branch (`latest`) and version tags (`vX.Y.Z`). Addresses the request in issue #389. --- .github/workflows/publish-docker.yml | 50 +++++++++++++++ apps/web/app/blog/page.tsx | 6 +- apps/web/app/blog/post/[slug]/page.tsx | 7 +++ apps/web/app/sitemap.ts | 7 +++ docker-compose.yml | 7 ++- docker/Dockerfile.prod | 87 ++++++++++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 10 ++- 8 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/publish-docker.yml create mode 100644 docker/Dockerfile.prod diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml new file mode 100644 index 0000000000..061adb2b61 --- /dev/null +++ b/.github/workflows/publish-docker.yml @@ -0,0 +1,50 @@ +name: Publish Docker Image + +on: + push: + branches: + - 'main' # Trigger on push to main branch + tags: + - 'v*.*.*' # Trigger on tags like v1.0.0 + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # Needed to push to GHCR + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/vishalmakwana111/inbox-zero + tags: | + # Push main branch commits as latest + type=raw,value=latest,enable={{is_default_branch}} + # Push version tags like v1.2.3, v1.2 for tags starting with v + type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + no-cache: true diff --git a/apps/web/app/blog/page.tsx b/apps/web/app/blog/page.tsx index 9a97d5994c..7112a73116 100644 --- a/apps/web/app/blog/page.tsx +++ b/apps/web/app/blog/page.tsx @@ -206,7 +206,11 @@ const mdxPosts: Post[] = [ export const revalidate = 60; export default async function BlogContentsPage() { - const posts = await sanityFetch({ query: postsQuery }); + // Skip Sanity fetch during build with dummy credentials + let posts: SanityPost[] = []; + if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID !== 'dummy-sanity-project-id-for-build') { + posts = await sanityFetch({ query: postsQuery }); + } return ( diff --git a/apps/web/app/blog/post/[slug]/page.tsx b/apps/web/app/blog/post/[slug]/page.tsx index 0a097c2a84..8b680ff28e 100644 --- a/apps/web/app/blog/post/[slug]/page.tsx +++ b/apps/web/app/blog/post/[slug]/page.tsx @@ -10,6 +10,9 @@ import { captureException } from "@/utils/error"; export const revalidate = 60; export async function generateStaticParams() { + if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID === 'dummy-sanity-project-id-for-build') { + return []; + } const posts = await client.fetch(postPathsQuery); return posts; } @@ -67,5 +70,9 @@ export default async function Page(props: Props) { const params = await props.params; const post = await sanityFetch({ query: postQuery, params }); + if (!post) { + return
Blog post content unavailable.
; + } + return ; } diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts index cdabc7a1da..4007d30188 100644 --- a/apps/web/app/sitemap.ts +++ b/apps/web/app/sitemap.ts @@ -4,6 +4,13 @@ import { sanityFetch } from "@/sanity/lib/fetch"; import { postSlugsQuery } from "@/sanity/lib/queries"; async function getBlogPosts() { + // Skip Sanity fetch during build with dummy credentials + if ( + process.env.NEXT_PUBLIC_SANITY_PROJECT_ID === + "dummy-sanity-project-id-for-build" + ) { + return []; // Return empty array directly + } const posts = await sanityFetch<{ slug: string; date: string }[]>({ query: postSlugsQuery, }); diff --git a/docker-compose.yml b/docker-compose.yml index 87aefab556..c4ef9a9e0c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,9 +38,10 @@ services: - inbox-zero-network web: - build: - context: . - dockerfile: ./docker/Dockerfile.web + # build: + # context: . + # dockerfile: ./docker/Dockerfile.web + image: ghcr.io/vishalmakwana111/inbox-zero:latest env_file: - ./apps/web/.env depends_on: diff --git a/docker/Dockerfile.prod b/docker/Dockerfile.prod new file mode 100644 index 0000000000..8c838a04aa --- /dev/null +++ b/docker/Dockerfile.prod @@ -0,0 +1,87 @@ +FROM node:22-alpine + +WORKDIR /app + +# Install necessary tools +RUN apk add --no-cache openssl +RUN npm install -g pnpm + +# Copy all package manager files first for caching +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc* ./ +COPY apps/web/package.json apps/web/ +COPY apps/unsubscriber/package.json apps/unsubscriber/ +COPY apps/mcp-server/package.json apps/mcp-server/ +COPY packages/eslint-config/package.json packages/eslint-config/ +COPY packages/loops/package.json packages/loops/ +COPY packages/resend/package.json packages/resend/ +COPY packages/tinybird/package.json packages/tinybird/ +COPY packages/tinybird-ai-analytics/package.json packages/tinybird-ai-analytics/ +COPY packages/tsconfig/package.json packages/tsconfig/ + +# Copy the rest of the application code FIRST +COPY . . + +# Install ALL dependencies (including dev, no pruning) +# This will now run postinstall scripts *after* source code is copied +RUN pnpm install --frozen-lockfile + +# Set NODE_ENV for build and runtime +ENV NODE_ENV=production + +# Provide dummy build-time ENV VARS (Still needed for build) +ENV DATABASE_URL="postgresql://dummy:dummy@dummy:5432/dummy?schema=public" +ENV DIRECT_URL="postgresql://dummy:dummy@dummy:5432/dummy?schema=public" +ENV NEXTAUTH_SECRET="dummy_secret_for_build_only" +ENV NEXTAUTH_URL="http://localhost:3000" +ENV GOOGLE_CLIENT_ID="dummy_id_for_build_only" +ENV GOOGLE_CLIENT_SECRET="dummy_secret_for_build_only" +ENV GOOGLE_ENCRYPT_SECRET="dummy_encrypt_secret_for_build_only" +ENV GOOGLE_ENCRYPT_SALT="dummy_encrypt_salt_for_build_only" +ENV GOOGLE_PUBSUB_TOPIC_NAME="dummy_topic_for_build_only" +ENV GOOGLE_PUBSUB_VERIFICATION_TOKEN="dummy_pubsub_token_for_build" +ENV INTERNAL_API_KEY="dummy_apikey_for_build_only" +ENV API_KEY_SALT="dummy_salt_for_build_only" +ENV UPSTASH_REDIS_URL="http://dummy-redis-for-build:6379" +ENV UPSTASH_REDIS_TOKEN="dummy_redis_token_for_build" +ENV REDIS_URL="redis://dummy:dummy@dummy:6379" +ENV QSTASH_TOKEN="dummy_qstash_token_for_build" +ENV QSTASH_CURRENT_SIGNING_KEY="dummy_qstash_curr_key_for_build" +ENV QSTASH_NEXT_SIGNING_KEY="dummy_qstash_next_key_for_build" +ENV NEXT_PUBLIC_SANITY_PROJECT_ID="dummy-sanity-project-id-for-build" +ENV NEXT_PUBLIC_SANITY_DATASET="dummy-sanity-dataset-for-build" + +# Ensure prisma generate runs +RUN pnpm --filter inbox-zero-ai exec -- prisma generate + +# Provide dummy DB URLs for build-time Prisma schema loading +ENV DATABASE_URL="postgresql://dummy:dummy@dummy:5432/dummy?schema=public" +ENV DIRECT_URL="postgresql://dummy:dummy@dummy:5432/dummy?schema=public" +# Provide dummy values for other required build-time ENV VARS +ENV NEXTAUTH_SECRET="dummy_secret_for_build_only" +ENV GOOGLE_CLIENT_ID="dummy_id_for_build_only" +ENV GOOGLE_CLIENT_SECRET="dummy_secret_for_build_only" +ENV GOOGLE_ENCRYPT_SECRET="dummy_encrypt_secret_for_build_only" +ENV GOOGLE_ENCRYPT_SALT="dummy_encrypt_salt_for_build_only" +ENV GOOGLE_PUBSUB_TOPIC_NAME="dummy_topic_for_build_only" +ENV INTERNAL_API_KEY="dummy_apikey_for_build_only" + +# Provide dummy Redis/QStash vars needed for build analysis +ENV UPSTASH_REDIS_URL="http://dummy-redis-for-build:6379" +ENV UPSTASH_REDIS_TOKEN="dummy_redis_token_for_build" +ENV QSTASH_CURRENT_SIGNING_KEY="dummy_qstash_curr_key_for_build" +ENV QSTASH_NEXT_SIGNING_KEY="dummy_qstash_next_key_for_build" +ENV QSTASH_TOKEN="dummy_qstash_token_for_build" + +# Provide dummy Sanity vars needed for build +ENV NEXT_PUBLIC_SANITY_PROJECT_ID="dummy_sanity_id_for_build" +ENV NEXT_PUBLIC_SANITY_DATASET="dummy_sanity_dataset_for_build" + +# Build the Next.js application only (skip prisma migrate deploy during build) +RUN pnpm --filter inbox-zero-ai exec -- next build + +# Expose port 3000 +EXPOSE 3000 + +# Set the default command to start the production server +# Use the simpler pnpm command, should work now as pnpm & next are installed +CMD pnpm --filter inbox-zero-ai start \ No newline at end of file diff --git a/package.json b/package.json index e131648283..a7ec7105da 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "husky": "9.1.7", "lint-staged": "15.5.1", "prettier": "3.5.3", + "prettier-plugin-tailwindcss": "0.6.11", "turbo": "2.5.0" }, "packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd8bc7a5a9..d8d44b84c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: prettier: specifier: 3.5.3 version: 3.5.3 + prettier-plugin-tailwindcss: + specifier: 0.6.11 + version: 0.6.11(prettier@3.5.3) turbo: specifier: 2.5.0 version: 2.5.0 @@ -9409,6 +9412,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} @@ -19785,7 +19789,7 @@ snapshots: '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.6.1)(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react: 7.37.4(eslint@9.24.0(jiti@2.4.2)) @@ -19839,7 +19843,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)): dependencies: debug: 4.4.0(supports-color@8.1.1) enhanced-resolve: 5.18.1 @@ -19874,7 +19878,7 @@ snapshots: '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.24.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color From 9d179161f76242a743f22693c37d007c65db7cf9 Mon Sep 17 00:00:00 2001 From: Elie Steinbock <3090527+elie222@users.noreply.github.com> Date: Sun, 4 May 2025 06:47:17 -0700 Subject: [PATCH 2/3] Update .github/workflows/publish-docker.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/workflows/publish-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 061adb2b61..e6f613e353 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -23,7 +23,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/vishalmakwana111/inbox-zero + images: ghcr.io/${{ github.repository_owner }}/inbox-zero tags: | # Push main branch commits as latest type=raw,value=latest,enable={{is_default_branch}} From 4473a7b3965f7454764c355e713b26c24e4dcaa8 Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Sun, 4 May 2025 07:42:50 -0700 Subject: [PATCH 3/3] adjust docker file --- .github/workflows/publish-docker.yml | 4 ++-- docker-compose.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index e6f613e353..dc7ee006d6 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -3,9 +3,9 @@ name: Publish Docker Image on: push: branches: - - 'main' # Trigger on push to main branch + - "main" # Trigger on push to main branch tags: - - 'v*.*.*' # Trigger on tags like v1.0.0 + - "v*.*.*" # Trigger on tags like v1.0.0 jobs: build-and-push: diff --git a/docker-compose.yml b/docker-compose.yml index c4ef9a9e0c..c3d750681c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,10 +38,10 @@ services: - inbox-zero-network web: - # build: - # context: . - # dockerfile: ./docker/Dockerfile.web - image: ghcr.io/vishalmakwana111/inbox-zero:latest + build: + context: . + dockerfile: ./docker/Dockerfile.web + # image: ghcr.io/elie222/inbox-zero:latest env_file: - ./apps/web/.env depends_on: