Skip to content

Instantly share code, notes, and snippets.

@Clumsy-Coder
Created November 4, 2023 19:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Clumsy-Coder/08f5e0f93f38e726c6b371142159b504 to your computer and use it in GitHub Desktop.
Save Clumsy-Coder/08f5e0f93f38e726c6b371142159b504 to your computer and use it in GitHub Desktop.
github-actions NextJS docker build with semantic-release
---
# .github/workflows/build.yaml
name: NextJS build and release
# description: lint, test, build and release NextJS
on: push
env:
FORCE_COLOR: true # display terminal colors
# APP_NAME: app_name
# GHCR_IMAGE: ghcr.io/<user>/<repo name>
CONTAINER_REGISTRY: ghcr.io
IMAGE_NAME: clumsy-coder/pihole-dashboard
####################################################################################################
jobs:
# install npm packages and store them as cache.
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: latest
- name: Cache node modules
id: cache-primes
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
# skip npm ci if `package.json` didn't change
# https://github.com/actions/cache#outputs
# https://github.com/actions/cache#restoring-and-saving-cache-using-a-single-action
- name: Install npm dependencies
if: steps.cache-primes.outputs.cache-hit != 'true'
run: npm ci --include=dev
################################################################################################
# lint source code using ESlint and Typescript
lint:
needs: install
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: latest
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- name: Lint project
run: npm run lint
################################################################################################
# prepare docker built
# extract the next version tag
prepare-docker-build:
needs: install
runs-on: ubuntu-latest
outputs:
NEXT_VERSION: ${{ steps.set-env.outputs.NEXT_VERSION }}
PUBLISH: ${{ steps.set-env.outputs.PUBLISH }}
BUILD_DATE: ${{ steps.set-env.outputs.BUILD_DATE }}
GIT_SHA: ${{ steps.set-env.outputs.GIT_SHA }}
GIT_REF: ${{ steps.set-env.outputs.GIT_REF }}
new-release-published: ${{ steps.get-next-version.outputs.new-release-published }}
new-release-version: ${{ steps.get-next-version.outputs.new-release-version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: latest
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- name: Extract next semantic-release version
# run: npx semantic-release --dry-run --branches="*"
run: npx semantic-release --dry-run
id: get-next-version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# needed in case semantic-release doesn't run on branches other than 'master' or 'development'
- name: Set NEXT_VERSION if there's NO new release
if: |
steps.get-next-version.outputs.new-release-published == '' ||
steps.get-next-version.outputs.new-release-published == 'false'
run: |
node -p "require('./package').version"
node -p "require('./package').version" | awk '{print "NEXT_VERSION=" $1}' >> "$GITHUB_ENV"
echo "PUBLISH=false" >> "$GITHUB_ENV"
- name: Set NEXT_VERSION if there's a NEW release
if: steps.get-next-version.outputs.new-release-published == 'true'
run: |
echo ${{ steps.get-next-version.outputs.new-release-version }}
echo "NEXT_VERSION=${{ steps.get-next-version.outputs.new-release-version }}" >> "$GITHUB_ENV"
echo "PUBLISH=true" >> "$GITHUB_ENV"
- name: Set Environment Variables
id: set-env
run: |
{
echo "NEXT_VERSION=$NEXT_VERSION"
echo "PUBLISH=$PUBLISH"
echo "BUILD_DATE=$(date +'%Y-%m-%d %H:%M:%S')"
echo "GIT_SHA=$(echo ${{ github.sha }} | cut -c1-7)"
# replace `/` with `-`
# mainly used for branches created by dependabot. they contain `/` which docker tagname cannot have
# https://docs.docker.com/engine/reference/commandline/tag/#description
# https://stackoverflow.com/a/73799519/3053548
# https://stackoverflow.com/a/73467468/3053548
echo "GIT_REF=${GITHUB_REF_NAME//\//-}"
# echo "GHCR_IMAGE=$(echo 'console.log("ghcr.io/${{ github.repository }}".toLowerCase())' | node -)" >> $GITHUB_ENV
} | tee -a "$GITHUB_ENV" "$GITHUB_OUTPUT"
- run: echo "$GITHUB_ENV"
################################################################################################
# build docker images using reusable workflow and github action matrix
docker-build-image:
needs: prepare-docker-build
strategy:
matrix:
# using paired matrix values
# obtained from
# https://stackoverflow.com/a/76547617/3053548
include:
- version: latest
publish: ${{ needs.prepare-docker-build.outputs.publish == 'true' }}
# next version
- version: ${{ needs.prepare-docker-build.outputs.next_version }}
publish: ${{ needs.prepare-docker-build.outputs.publish == 'true' }}
# nightly
- version: nightly
publish: ${{ github.ref == 'refs/heads/development' }}
# branches that are not `development` or `master`
# used for testing code of development branches
- version: ${{ needs.prepare-docker-build.outputs.git_ref }}
publish: ${{ github.ref != 'refs/heads/development' && github.ref != 'refs/heads/master' && github.actor != 'dependabot[bot]' }}
uses: ./.github/workflows/reusable-docker-build.yml
with:
publish: ${{ matrix.publish }}
tags: ${{ matrix.version }}
secrets: inherit
################################################################################################
# verify matrix job results
# since branch protection rules can't group the job matrix together as a required status check.
# this will serve as a workaround
# code obtained from
# - https://github.com/orgs/community/discussions/26822#discussioncomment-5122101
matrix-results:
needs: docker-build-image
runs-on: ubuntu-latest
name: verify job matrix results
if: always()
steps:
- run: exit 1
# see https://stackoverflow.com/a/67532120/4907315
if: >-
${{
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
}}
################################################################################################
semantic-release:
# prerequisite of a job that uses matrix
# https://github.com/orgs/community/discussions/42010#discussioncomment-4439644
needs: docker-build-image
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v3
with:
node-version: latest
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- run: npm install --production=false
- name: semantic-release
run: npx semantic-release --ci
env:
GITHUB_TOKEN: ${{ secrets.DEPENDABOT_TOKEN }}
####################################################################################################
---
# .github/workflows/cleanup-docker-image.yml
# remove docker image that's NOT from `master` or `development`
#
# branch created from `development` will create docker image with the tagname of the branch name
# after a pull request is merged to the `development` branch, this github action will remove
# docker image with the tagname of the `development` branch
#
# Ex:
# - branch name `620` from `development` branch is created
# - committed some code
# - github action creates docker image with tagname `620`
# - created a pull request
# - after pull request closed and merged, this github action will remove the docker image with tagname `620`
#
# code obtained from
# - extracting json property conditionally
# - https://stackoverflow.com/a/47887662/3053548
# - GET list of docker images
# - https://docs.github.com/en/rest/packages/packages?apiVersion=2022-11-28#list-package-versions-for-a-package-owned-by-a-user
# - github action to extract version ID
# - https://github.com/actions/delete-package-versions/issues/101#issuecomment-1553166050
name: Cleanup docker image on PR merge
on:
pull_request_target:
# types:
# - closed
# - opened
branches-ignore:
- master
env:
FORCE_COLOR: true
CONTAINER_REGISTRY: ghcr.io
IMAGE_NAME: clumsy-coder/pihole-dashboard
PACKAGE_NAME: pihole-dashboard
permissions:
# needed for pushing docker images from branches created by dependabot[bot].
# check
# - https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs#defining-access-for-the-github_token-scopes
# - https://github.blog/changelog/2021-10-06-github-actions-workflows-triggered-by-dependabot-prs-will-respect-permissions-key-in-workflows/
# - https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#responding-to-events
#
# check issue
# - #649
# check commit
# - 1548272
# - cc87aba
# - 5f9737d
packages: write
pull-requests: write
jobs:
remove-dev-image:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
steps:
- name: replace `/` with `-` in branch name
# mainly used for branches created by dependabot. they contain `/` which docker tagname cannot have
# https://docs.docker.com/engine/reference/commandline/tag/#description
# https://stackoverflow.com/a/73799519/3053548
# https://stackoverflow.com/a/73467468/3053548
# check issue:
# - #649
# check commit
# - 1548272
# - cc87aba
run: echo "HEAD_REF=${HEAD_REF//\//-}" >> "$GITHUB_ENV"
- run: echo "$GITHUB_ENV"
- name: Get Version ID of docker image
run: |
curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.DEPENDABOT_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/users/${{ github.repository_owner }}/packages/container/${{ env.PACKAGE_NAME }}/versions >> containerMeta.json ;
echo "VERSION_ID=$(jq -r '.[] | select(.metadata.container.tags[] == "${{ env.HEAD_REF }}").id' containerMeta.json)" >> "$GITHUB_ENV" ;
- name: Print version ID
run: echo ${{ env.VERSION_ID }}
- name: Remove pull request image from container registry
uses: actions/delete-package-versions@v4
if: ${{ env.VERSION_ID != '' }}
with:
package-name: ${{ env.PACKAGE_NAME }}
package-type: 'container'
package-version-ids: "${{ env.VERSION_ID }}"
---
# .github/workflows/dependabot.yml
name: Dependabot automerge
on: pull_request
# description: Automerge dependabot pull requests
#
# auto merge Dependabot pull requests
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request
jobs:
automerge-pr:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
if: |
github.actor == 'dependabot[bot]' &&
(
startsWith(github.event.pull_request.title, 'build(deps):') ||
startsWith(github.event.pull_request.title, 'build(devDep):') ||
startsWith(github.event.pull_request.title, 'ci(github-action):') ||
startsWith(github.event.pull_request.title, 'ci(docker):')
)
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: '${{ secrets.GITHUB_TOKEN }}'
# make sure to enable 'auto-merge' in the repo
# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-auto-merge-for-pull-requests-in-your-repository
- name: Enable auto-merge for Dependabot PRs
run: gh pr merge --auto --rebase "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
---
# .github/workflows/reusable-docker-build.yml
name: Build and push Docker image
# When calling this workflow, ensure you use
# secrets: inherit
# So the DOCKER_USERNAME and DOCKER_PASSWORD are available.
on:
workflow_call:
inputs:
publish:
type: boolean
description: Whether to publish the image to Github Registry
required: false
default: false
tags:
type: string
required: true
description: docker image version
# labels:
# type: string
# required: true
# description: docker image labels
env:
FORCE_COLOR: true
CONTAINER_REGISTRY: ghcr.io
IMAGE_NAME: clumsy-coder/pihole-dashboard
PACKAGE_NAME: pihole-dashboard
permissions:
# needed for pushing docker images from branches created by dependabot[bot].
# check
# - https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs#defining-access-for-the-github_token-scopes
# - https://github.blog/changelog/2021-10-06-github-actions-workflows-triggered-by-dependabot-prs-will-respect-permissions-key-in-workflows/
# - https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#responding-to-events
#
# check issue
# - #649
# check commit
# - 1548272
# - cc87aba
# - 5f9737d
packages: write
jobs:
build-and-push:
name: Docker Build and Push
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: generate GIT_SHA and GIT_REF to GITHUB_ENV
run: |
{
echo "GIT_SHA=$(echo ${{ github.sha }} | cut -c1-7)"
echo "GIT_REF=$(git symbolic-ref -q --short HEAD || git describe --tags --exact-match)"
} >> "$GITHUB_ENV"
- name: Create .env.local for NextJS
run: |
{
printf "NEXT_PUBLIC_BUILD_VERSION=%s\n" "${{ inputs.tags }}"
printf "NEXT_PUBLIC_BUILD_ID=%s\n" "$(echo ${{ github.sha }} | cut -c -7)"
printf "SECRET_COOKIE_PASSWORD=%s\n" "${{ secrets.SECRET_COOKIE_PASSWORD }}"
printf "SECURE_COOKIE_TTL=%s\n" "${{ secrets.SECURE_COOKIE_TTL }}"
printf "NEXT_PUBLIC_POLLING_AUTH_SESSION=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_AUTH_SESSION }}"
printf "NEXT_PUBLIC_POLLING_SUMMARY=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_SUMMARY }}"
printf "NEXT_PUBLIC_POLLING_FORWARDED_DESTINATIONS=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_FORWARDED_DESTINATIONS }}"
printf "NEXT_PUBLIC_POLLING_QUERY_TYPES=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_QUERY_TYPES }}"
printf "NEXT_PUBLIC_POLLING_TOP_PERMITTED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_TOP_PERMITTED_QUERIES }}"
printf "NEXT_PUBLIC_POLLING_TOP_BLOCKED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_TOP_BLOCKED_QUERIES }}"
printf "NEXT_PUBLIC_NUM_ENTRIES_TOP_PERMITTED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_NUM_ENTRIES_TOP_PERMITTED_QUERIES }}"
printf "NEXT_PUBLIC_NUM_ENTRIES_TOP_BLOCKED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_NUM_ENTRIES_TOP_BLOCKED_QUERIES }}"
printf "NEXT_PUBLIC_POLLING_TOP_CLIENTS_ALLOWED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_TOP_CLIENTS_ALLOWED_QUERIES }}"
printf "NEXT_PUBLIC_POLLING_TOP_CLIENTS_BLOCKED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_TOP_CLIENTS_BLOCKED_QUERIES }}"
printf "NEXT_PUBLIC_NUM_ENTRIES_TOP_CLIENTS_ALLOWED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_NUM_ENTRIES_TOP_CLIENTS_ALLOWED_QUERIES }}"
printf "NEXT_PUBLIC_NUM_ENTRIES_TOP_CLIENTS_BLOCKED_QUERIES=%s\n" "${{ secrets.NEXT_PUBLIC_NUM_ENTRIES_TOP_CLIENTS_BLOCKED_QUERIES }}"
printf "NEXT_PUBLIC_POLLING_QUERIES_OVERTIME=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_QUERIES_OVERTIME }}"
printf "NEXT_PUBLIC_POLLING_CLIENTS_OVERTIME=%s\n" "${{ secrets.NEXT_PUBLIC_POLLING_CLIENTS_OVERTIME }}"
printf "NEXT_PUBLIC_BUILD_TIME=%s\n" "$(date +%s)"
} >> .env.local
cat .env.local
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker image metadata
id: meta
uses: docker/metadata-action@v5.0.0
with:
images: ${{ env.CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}
labels: |
org.opencontainers.image.authors=${{ github.repository_owner }}
org.opencontainers.image.ref.name=${{ env.GIT_REF }}
org.opencontainers.image.version=${{ inputs.tags }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./dockerfile
pull: true
# push: ${{ inputs.publish }}
load: true
tags: ${{ env.CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ inputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: View current docker images
run: docker images
- name: Push docker image
if: ${{ inputs.publish }}
run: docker push ${{ env.CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ inputs.tags }}
- name: Remove untagged image on container registry
if: ${{ inputs.publish }}
uses: actions/delete-package-versions@v4
with:
package-name: ${{ env.PACKAGE_NAME }}
package-type: 'container'
delete-only-untagged-versions: 'true'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment