Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

82 feature request suggestion create fe image workflow #99

Merged
99 changes: 99 additions & 0 deletions .github/workflows/build_and_push_image.yml
Original file line number Diff line number Diff line change
@@ -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 }}"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# vscode
.vscode/

64 changes: 37 additions & 27 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
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
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

Expand All @@ -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"]
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)
98 changes: 98 additions & 0 deletions docs/runbooks/Container-README.md
Original file line number Diff line number Diff line change
@@ -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/<branch>: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 <your_github_username> -p <your_PAT>
```

- 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/<branch>:latest`

Where:

- `refactor-group` is your GitHub organization.
- `refactor-platform-fe` is the repository name.
- `<branch>` 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 <your_github_username> -p <your_PAT> # 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:<branch> --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:<branch> --load . # Builds for specified architectures and loads to local docker daemon

# 8. Tag the image
docker tag ghcr.io/refactor-group/refactor-platform-fe:<branch> ghcr.io/refactor-group/refactor-platform-fe:<branch>:latest # Creates an additional tag 'latest' for the image

# 9. Push the image
docker push ghcr.io/refactor-group/refactor-platform-fe:<branch>:latest # Uploads the image to the container registry

# 10. Pull the image
docker pull ghcr.io/refactor-group/refactor-platform-fe:<branch>:latest # Downloads the image from the container registry

# 11. Run the image
docker run -p 3000:3000 ghcr.io/refactor-group/refactor-platform-fe:<branch>:latest # Starts a container from the image, mapping port 3000

# 12. Inspect the image
docker inspect ghcr.io/refactor-group/refactor-platform-fe:<branch>:latest # Shows detailed information about the image

# 13. Remove the image
docker rmi ghcr.io/refactor-group/refactor-platform-fe:<branch>: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