diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a741e33 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +target +.git +.github +docs +samples +boardflow/node_modules +boardflow/.next diff --git a/.github/workflows/backend-image.yml b/.github/workflows/backend-image.yml new file mode 100644 index 0000000..80fec31 --- /dev/null +++ b/.github/workflows/backend-image.yml @@ -0,0 +1,75 @@ +name: Backend Images + +on: + pull_request: + branches: + - main + paths: + - .github/workflows/backend-image.yml + - crates/** + - Cargo.toml + - Cargo.lock + push: + branches: + - main + tags: + - v* + paths: + - .github/workflows/backend-image.yml + - crates/** + - Cargo.toml + - Cargo.lock + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + DOCKER_METADATA_SHORT_SHA_LENGTH: 12 + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + service: + - name: api + dockerfile: crates/api/Dockerfile + - name: worker + dockerfile: crates/worker/Dockerfile + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v4 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.REGISTRY }}/f0reacharr/boardflow-${{ matrix.service.name }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix=sha- + + - name: Build and publish ${{ matrix.service.name }} image + uses: docker/build-push-action@v7 + with: + context: . + file: ${{ matrix.service.dockerfile }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=backend-${{ matrix.service.name }} + cache-to: type=gha,mode=max,scope=backend-${{ matrix.service.name }} diff --git a/.github/workflows/frontend-image.yml b/.github/workflows/frontend-image.yml new file mode 100644 index 0000000..8c025d4 --- /dev/null +++ b/.github/workflows/frontend-image.yml @@ -0,0 +1,65 @@ +name: Frontend Image + +on: + pull_request: + branches: + - main + paths: + - .github/workflows/frontend-image.yml + - boardflow/** + push: + branches: + - main + tags: + - v* + paths: + - .github/workflows/frontend-image.yml + - boardflow/** + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: f0reacharr/boardflow-frontend + DOCKER_METADATA_SHORT_SHA_LENGTH: 12 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v4 + + - name: Log in to GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix=sha- + + - name: Build and publish frontend image + uses: docker/build-push-action@v7 + with: + context: boardflow + file: boardflow/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/boardflow/.dockerignore b/boardflow/.dockerignore new file mode 100644 index 0000000..7ed2902 --- /dev/null +++ b/boardflow/.dockerignore @@ -0,0 +1,4 @@ +node_modules +.next +.git +*.tsbuildinfo diff --git a/boardflow/Dockerfile b/boardflow/Dockerfile new file mode 100644 index 0000000..ddf0d38 --- /dev/null +++ b/boardflow/Dockerfile @@ -0,0 +1,34 @@ +# Stage 1: Install dependencies +FROM node:22-slim AS deps +WORKDIR /app +RUN corepack enable +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +# Stage 2: Build +FROM node:22-slim AS builder +WORKDIR /app +RUN corepack enable +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN pnpm build + +# Stage 3: Runtime +FROM node:22-slim AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +RUN useradd --system --create-home --shell /usr/sbin/nologin nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nextjs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nextjs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENTRYPOINT ["node", "server.js"] diff --git a/boardflow/next.config.ts b/boardflow/next.config.ts index e0cca9b..fa8173d 100644 --- a/boardflow/next.config.ts +++ b/boardflow/next.config.ts @@ -1,6 +1,7 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { + output: 'standalone', experimental: { optimizePackageImports: ['@chakra-ui/react'], }, diff --git a/boardflow/package.json b/boardflow/package.json index 4d7715f..ebdc23b 100644 --- a/boardflow/package.json +++ b/boardflow/package.json @@ -2,6 +2,12 @@ "name": "boardflow-frontend", "version": "0.1.0", "private": true, + "packageManager": "pnpm@10.33.2", + "pnpm": { + "onlyBuiltDependencies": [ + "sharp" + ] + }, "scripts": { "dev": "next dev", "build": "next build", diff --git a/crates/api/Dockerfile b/crates/api/Dockerfile new file mode 100644 index 0000000..68fb2d1 --- /dev/null +++ b/crates/api/Dockerfile @@ -0,0 +1,38 @@ +# Stage 1: Plan dependencies +FROM rust:1.95-bookworm AS planner +WORKDIR /app +RUN cargo install cargo-chef --locked +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +# Stage 2: Cache dependencies +FROM rust:1.95-bookworm AS cacher +WORKDIR /app +RUN cargo install cargo-chef --locked +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json -p boardflow-api + +# Stage 3: Build binary +FROM rust:1.95-bookworm AS builder +WORKDIR /app +COPY . . +COPY --from=cacher /app/target target +COPY --from=cacher /usr/local/cargo /usr/local/cargo +RUN cargo build --release -p boardflow-api + +# Stage 4: Runtime +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd --system --create-home --shell /usr/sbin/nologin boardflow +USER boardflow + +COPY --from=builder /app/target/release/boardflow-api /usr/local/bin/boardflow-api + +EXPOSE 3000 + +ENTRYPOINT ["/usr/local/bin/boardflow-api"] diff --git a/crates/worker/Dockerfile b/crates/worker/Dockerfile new file mode 100644 index 0000000..3ddfedb --- /dev/null +++ b/crates/worker/Dockerfile @@ -0,0 +1,36 @@ +# Stage 1: Plan dependencies +FROM rust:1.95-bookworm AS planner +WORKDIR /app +RUN cargo install cargo-chef --locked +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +# Stage 2: Cache dependencies +FROM rust:1.95-bookworm AS cacher +WORKDIR /app +RUN cargo install cargo-chef --locked +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json -p boardflow-worker + +# Stage 3: Build binary +FROM rust:1.95-bookworm AS builder +WORKDIR /app +COPY . . +COPY --from=cacher /app/target target +COPY --from=cacher /usr/local/cargo /usr/local/cargo +RUN cargo build --release -p boardflow-worker + +# Stage 4: Runtime +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd --system --create-home --shell /usr/sbin/nologin boardflow +USER boardflow + +COPY --from=builder /app/target/release/boardflow-worker /usr/local/bin/boardflow-worker + +ENTRYPOINT ["/usr/local/bin/boardflow-worker"]