diff --git a/.github/workflows/build_and_push_image.yml b/.github/workflows/build_and_push_image.yml new file mode 100644 index 0000000..e073daf --- /dev/null +++ b/.github/workflows/build_and_push_image.yml @@ -0,0 +1,99 @@ +name: Build and Push Frontend Image + +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +jobs: + build_and_push_frontend: + runs-on: ubuntu-22.04 + + permissions: + contents: read + packages: write + id-token: write # Required for Cosign OIDC signing + + steps: + # Checkout the source code + - uses: actions/checkout@v4 + + # Setup QEMU for emulating multi-arch (e.g., ARM64 on x86) + - uses: docker/setup-qemu-action@v2 + with: + platforms: linux/amd64,linux/arm64 + + # Setup Buildx for advanced Docker builds (multiarch, caching, sbom) + - uses: docker/setup-buildx-action@v3 + with: + install: true + + # Login to GHCR (GitHub Container Registry) + - name: Docker login + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Dynamically generate image tag and name based on repo/org/branch + - name: Determine Image Tags + id: tags + run: | + BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} + ORG_NAME="refactor-group" + REPO_NAME="refactor-platform-fe" + IMAGE="${{ env.REGISTRY }}/${ORG_NAME}/${REPO_NAME}/${BRANCH_NAME}:latest" + echo "tag=$IMAGE" >> $GITHUB_OUTPUT + echo "image=$IMAGE" >> $GITHUB_OUTPUT + + # Build, SBOM, and Push the multi-arch Docker image + - name: Build + Push Frontend + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile # Dockerfile is at the root of the repo + target: runner # Your Dockerfile defines this stage + platforms: linux/amd64,linux/arm64 + push: true + provenance: true # Enables provenance metadata + sbom: true # Enables SBOM generation + build-args: | + NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL=${{ secrets.BACKEND_SERVICE_PROTOCOL }} + NEXT_PUBLIC_BACKEND_SERVICE_HOST=${{ secrets.BACKEND_SERVICE_HOST }} + NEXT_PUBLIC_BACKEND_SERVICE_PORT=${{ secrets.BACKEND_PORT }} + NEXT_PUBLIC_BACKEND_API_VERSION=${{ secrets.BACKEND_API_VERSION }} + FRONTEND_SERVICE_PORT=${{ secrets.FRONTEND_SERVICE_PORT }} + FRONTEND_SERVICE_INTERFACE=${{ secrets.FRONTEND_SERVICE_INTERFACE }} + tags: ${{ steps.tags.outputs.tag }} + cache-from: type=gha # GitHub-hosted build cache + cache-to: type=gha,mode=max + + # Install Cosign CLI for image signing + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + + # Sign image using GitHub OIDC token (no secrets needed) + - name: Sign image with Cosign + env: + COSIGN_EXPERIMENTAL: "true" + run: | + cosign sign --yes ${{ steps.tags.outputs.image }} + + # Output usage instructions + - name: Print Pull & Run Instructions + run: | + echo -e "\033[1;32mFrontend Image Pushed & Signed:\033[0m" + echo " docker pull ${{ steps.tags.outputs.image }}" + echo "" + echo -e "\033[1;36mRun locally or with Compose:\033[0m" + echo " docker run --rm --env-file .env -p 3000:3000 ${{ steps.tags.outputs.image }}" + echo "" + echo -e "\033[1;33mSignature Verification:\033[0m" + echo " cosign verify ${{ steps.tags.outputs.image }}" diff --git a/.gitignore b/.gitignore index fd3dbb5..fc1e7f0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,7 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# vscode +.vscode/ + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index afe4c5e..9769bc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,29 @@ -FROM node:18-alpine AS base +# Stage 0: Base image +FROM node:22-alpine3.19 AS base -# 1. Install dependencies only when needed +# BuildKit Platform Context (used for metadata, not to alter FROM) +ARG BUILDPLATFORM +ARG TARGETPLATFORM + +# Optional diagnostics (doesn't affect final image) +RUN echo "Build Platform: ${BUILDPLATFORM} -> Target Platform: ${TARGETPLATFORM}" + +# Stage 1: Dependencies FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat +RUN apk update && apk upgrade --no-cache && apk add --no-cache libc6-compat -# Set the working directory WORKDIR /app - -# Copy package.json and package-lock.json COPY package.json ./ COPY package-lock.json ./ - RUN npm install -# 2. Rebuild the source code only when needed +# Stage 2: Builder FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -# Receive the build args from docker-compose.yaml +# Build-time args from docker-compose or GitHub Actions ARG NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL ARG NEXT_PUBLIC_BACKEND_SERVICE_HOST ARG NEXT_PUBLIC_BACKEND_SERVICE_PORT @@ -28,22 +31,26 @@ ARG NEXT_PUBLIC_BACKEND_API_VERSION ARG FRONTEND_SERVICE_INTERFACE ARG FRONTEND_SERVICE_PORT -# And convert them to environment variables for runtime +# Pass them as ENV so Next.js static build can access ENV NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL=$NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL ENV NEXT_PUBLIC_BACKEND_SERVICE_HOST=$NEXT_PUBLIC_BACKEND_SERVICE_HOST ENV NEXT_PUBLIC_BACKEND_SERVICE_PORT=$NEXT_PUBLIC_BACKEND_SERVICE_PORT ENV NEXT_PUBLIC_BACKEND_API_VERSION=$NEXT_PUBLIC_BACKEND_API_VERSION -RUN echo "NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL: ${NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL}" -RUN echo "NEXT_PUBLIC_BACKEND_SERVICE_HOST: ${NEXT_PUBLIC_BACKEND_SERVICE_HOST}" -RUN echo "NEXT_PUBLIC_BACKEND_SERVICE_HOST: ${NEXT_PUBLIC_BACKEND_SERVICE_PORT}" -RUN echo "NEXT_PUBLIC_BACKEND_SERVICE_HOST: ${NEXT_PUBLIC_BACKEND_API_VERSION}" -RUN echo "FRONTEND_SERVICE_INTERFACE: ${FRONTEND_SERVICE_INTERFACE}}" -RUN echo "FRONTEND_SERVICE_PORT: ${FRONTEND_SERVICE_PORT}}" - +# Optional: Print the values to verify they are set correctly +RUN echo "Building with the following environment variables:" && \ + echo "NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL: ${NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL}" && \ + echo "NEXT_PUBLIC_BACKEND_SERVICE_HOST: ${NEXT_PUBLIC_BACKEND_SERVICE_HOST}" && \ + echo "NEXT_PUBLIC_BACKEND_SERVICE_PORT: ${NEXT_PUBLIC_BACKEND_SERVICE_PORT}" && \ + echo "NEXT_PUBLIC_BACKEND_API_VERSION: ${NEXT_PUBLIC_BACKEND_API_VERSION}" && \ + echo "FRONTEND_SERVICE_INTERFACE: ${FRONTEND_SERVICE_INTERFACE}" && \ + echo "FRONTEND_SERVICE_PORT: ${FRONTEND_SERVICE_PORT}" + +# Build the Next.js application +# Note: Use `next build` to build the application for production RUN npm run build -# 3. Production image, copy all the files and run next +# Stage 3: Runtime Image FROM base AS runner WORKDIR /app @@ -53,21 +60,24 @@ RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 COPY --from=builder /app/public ./public - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - COPY --from=builder /app/package.json ./ USER nextjs -# Expose the port Next.js runs on -EXPOSE $FRONTEND_SERVICE_PORT +# Expose the port +EXPOSE 3000 +ARG FRONTEND_SERVICE_INTERFACE +ARG FRONTEND_SERVICE_PORT + +# Runtime ENV for Compose ENV HOSTNAME=$FRONTEND_SERVICE_INTERFACE ENV PORT=$FRONTEND_SERVICE_PORT -# Run the app using JSON array notation -CMD ["node", "server.js"] +# executable that will run the application +ENTRYPOINT ["node"] + +# default args to the ENTRYPOINT that can be overridden at runtime +CMD ["server.js"] diff --git a/README.md b/README.md index 1670933..a3e901f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Refactor Coaching & Mentoring Platform -### Frontend (currently web browser-only) + +## Frontend (currently web browser-only) ![377960688-0b5292b0-6ec7-4774-984e-8e99e503d26c](https://github.com/user-attachments/assets/5dcdee09-802e-4b25-aa58-757d607ce7bc) A preview of the main coaching session page (rapidly evolving) @@ -43,3 +44,4 @@ npm run dev Open [http://localhost:3000](http://localhost:3000) with your browser to log in to the platform. +#### For Working with and Running the Application in Docker, navigate to the [Container-README](./docs/runbooks/Container-README.md) diff --git a/docs/runbooks/Container-README.md b/docs/runbooks/Container-README.md new file mode 100644 index 0000000..a7080a2 --- /dev/null +++ b/docs/runbooks/Container-README.md @@ -0,0 +1,98 @@ +# Refactor Platform Frontend Docker Image Management + +## Managing the Project with Docker & GitHub Actions (CI/CD) + +This project builds and pushes a multi-arch Docker image to [GHCR](https://github.com/orgs/refactor-group/packages) and triggers a build workflow on GitHub automatically on pushes to branches with open pull requests. + +**Key Steps:** + +1. **Develop Your Changes:** + - Work on your feature or fix in your branch. +2. **Open a Pull Request:** + - When you open a PR, GitHub Actions triggers the build workflow. +3. **Automated Build & Push:** + - The workflow builds a multi-arch Docker image and pushes it to GHCR. + - Verify the build status in GitHub Actions. + - The images are named as follows: `ghcr.io/refactor-group/refactor-platform-fe/:latest` +4. **Local Testing (Optional):** + - For local Docker testing, note that the Docker Compose file and environment variables are maintained in the backend repository. Make sure you have access to that repo for configuration details. + +--- + +## Manual Docker Image Management + +### If you plan on working with the Docker Image Locally as well the following section acts as a quickstart guide for managing the Docker images manually + +#### Prerequisites + +- Before running any Docker commands, ensure you are logged into GHCR using your GitHub personal access token (PAT): + +```bash +docker login ghcr.io -u -p +``` + +- Ensure you have Containerd installed and running +- Ensure you have Docker Buildx installed and configured +- Ensure you are using the builder instance you create using steps 1-4 below +- Ensure you have Docker installed and running +**Image Naming Convention:** +The Docker images follow the naming convention: +`ghcr.io/refactor-group/refactor-platform-fe/:latest` + +Where: + +- `refactor-group` is your GitHub organization. +- `refactor-platform-fe` is the repository name. +- `` is the name of the branch, with underscores converted to dashes. +- `latest` is the tag. + +**Useful Commands:** + +```bash +# Docker Buildx: Enhanced Image Management + +# 1. Inspect Docker Buildx +docker buildx version # Verify Docker Buildx is installed + +# 2. Create a new builder instance (if needed) +docker buildx create --name mybuilder --driver docker-container # Creates a builder instance named 'mybuilder' using the docker-container driver + +# 3. Use the builder +docker buildx use mybuilder # Sets 'mybuilder' as the current builder + +# 4. Inspect the builder +docker buildx inspect --bootstrap # Displays details and ensures the builder is running + +# 5. Login to GHCR +docker login ghcr.io -u -p # Authenticates with GitHub Container Registry using your username and PAT + +# 6. Build the image for multiple architectures and push to registry +docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/refactor-group/refactor-platform-fe: --push . # Builds for specified architectures and pushes to GHCR + +# 7. Build the image for multiple architectures and load to local docker daemon +docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/refactor-group/refactor-platform-fe: --load . # Builds for specified architectures and loads to local docker daemon + +# 8. Tag the image +docker tag ghcr.io/refactor-group/refactor-platform-fe: ghcr.io/refactor-group/refactor-platform-fe::latest # Creates an additional tag 'latest' for the image + +# 9. Push the image +docker push ghcr.io/refactor-group/refactor-platform-fe::latest # Uploads the image to the container registry + +# 10. Pull the image +docker pull ghcr.io/refactor-group/refactor-platform-fe::latest # Downloads the image from the container registry + +# 11. Run the image +docker run -p 3000:3000 ghcr.io/refactor-group/refactor-platform-fe::latest # Starts a container from the image, mapping port 3000 + +# 12. Inspect the image +docker inspect ghcr.io/refactor-group/refactor-platform-fe::latest # Shows detailed information about the image + +# 13. Remove the image +docker rmi ghcr.io/refactor-group/refactor-platform-fe::latest # Deletes the image from the local machine +``` + +### Important Notes + +- Always use the `latest` tag for the most recent version (it defaults to `latest`) +- Ensure your branch is up to date with the main branch before opening a PR +- For backend-specific configuration (Docker Compose & env vars), refer to the backend repository documentation