Expand
Solution:
lint:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run lint
run: npm run lintExplanation:
runs-on: ubuntu-24.04: This specifies that the job will run on the latest Ubuntu environment.actions/checkout@v4: This step checks out the repository's code.biomejs/setup-biome@v2: This sets up the Biome CLI tool to run linting.biome ci: This runs the Biome CLI with the ci command to check for linting issues.
Skills acquired:
- 📦 Basic GitHub Actions usage: You used
actions/checkout@v4to fetch the repository code, andactions/setup-node@v4to configure the Node.js environment. - 🧹 Code quality checks: You integrated a linter (Biome) to automatically detect style issues, bugs, and inconsistencies in the codebase.
- ⚡️ Workflow optimization: By using the npm cache with
cache: "npm", you’ve seen how caching can significantly reduce CI execution time.
Why it matters:
Linting enforces clean, consistent code formatting and helps prevent common issues early. Automating this step ensures that every contributor follows the same coding standards, reducing noise during code reviews.
Expand
Solution:
verify-typescript-types:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Verify typescript types
run: npm run typing-checkExplanation:
-
actions/setup-node@v4: This action sets up the Node.js environment, including caching npm dependencies to speed up future runs. -
npm ci: This installs the project's dependencies from the package-lock. -
npm run typing-check: This runs the type-checking script to ensure there are no TypeScript errors in the code.
Skills acquired:
- 👓 Static analysis: By running
npm run typing-check, you executed a static TypeScript check to catch potential issues without running the code. - 🔄 Reusing workflow patterns: This job follows the same structure as the linting one, showing how consistent, reusable workflow design makes pipelines easier to manage.
- 📘 Separation of concerns: Each job is focused on a single responsibility, improving clarity and maintainability of the CI configuration.
Why it matters:
Strong type checking helps catch bugs before runtime, increasing confidence in your codebase. Automating this guarantees type safety throughout the development lifecycle.
Expand
Solution:
code-coverage:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run code coverage
run: npm run coverage
- name: Report Coverage
if: always()
uses: davelosert/vitest-coverage-report-action@v2Explanation:
-
actions/setup-node@v4: This action sets up the Node.js environment, including caching npm dependencies to speed up future runs. -
npm ci: This installs the project's dependencies from the package-lock. -
npm run coverage: This command runs the tests and generates coverage reports. -
davelosert/vitest-coverage-report-action@v2: This action is used to report the coverage results. The if: always() ensures that the coverage report is generated regardless of whether the tests pass or fail.
Skills acquired:
- ✅ Running tests in CI: You configured GitHub Actions to automatically execute the test suite using
npm run coverage. - 📊 Generating and reporting code coverage: You used the third-party action
davelosert/vitest-coverage-report-action@v2to visualize coverage results. - 🧩 Using external actions: You explored how to integrate community actions to enhance CI/CD capabilities.
Why it matters:
Test coverage highlights which parts of the code are tested and which aren't, helping teams identify gaps and prioritize test writing. Reporting this coverage ensures visibility and encourages better test practices.
Expand
Solution:
build-and-push:
needs: [lint, verify-typescript-types, code-coverage]
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha,format=short
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=maxExplanation:
-
needs: [lint, verify-typescript-types, code-coverage]: This ensures the job only runs after the previous jobs have completed successfully, creating a pipeline. -
permissions: Explicitly sets the required permissions for the GITHUB_TOKEN to read repository contents and write to the GitHub Packages registry. -
docker/setup-buildx-action@v3: Sets up Docker Buildx, which provides enhanced build capabilities including better caching and multi-platform builds. -
docker/login-action@v3: Authenticates with GitHub Container Registry using the automatically provided GITHUB_TOKEN. -
docker/metadata-action@v5: Extracts metadata from Git to create appropriate tags and labels for the Docker image:type=ref,event=branch: Tags the image with the branch name (e.g.,main)type=sha,format=short: Tags the image with the short Git commit SHA for easier identification
-
docker/build-push-action@v5: Builds and pushes the Docker image with:- Multi-platform support for both AMD64 (standard x86 processors) and ARM64 (like Apple Silicon)
- GitHub Actions cache integration for faster builds
- Tags and labels from the metadata action
- Automatic push to the registry
Skills acquired:
- 🔄 CI/CD Pipeline Construction: You've created a complete pipeline from code quality checks to deployment, learning how jobs can depend on each other with the
needskeyword. - 🐳 Docker Integration: You've learned how to build and push multi-architecture Docker images (AMD64 and ARM64) as part of your CI/CD pipeline.
- 🔑 Secure Authentication: You've used GitHub's built-in token system to securely authenticate with the container registry without exposing credentials.
- 🏷️ Image Tagging Strategies: You've implemented best practices for versioning container images using Git metadata.
- 🚀 Deployment Automation: You've automated the deployment process, ensuring that only code that passes quality checks gets deployed.
Why it matters:
Containerization is a critical part of modern application deployment. By automating the build and push process, you ensure consistent, reproducible deployments and eliminate manual steps that could introduce errors. This completes the CI/CD pipeline, taking your code from commit to deployable artifact.
Using your container image:
Once pushed, your image will be available at ghcr.io/ekino/githubworkflow-handson-nodejs with two tags:
- Branch name tag:
ghcr.io/ekino/githubworkflow-handson-nodejs:main(or your branch name) - Short SHA tag:
ghcr.io/ekino/githubworkflow-handson-nodejs:a1b2c3d(abbreviated commit hash)
You can pull either version:
# Pull by branch name
docker pull ghcr.io/ekino/githubworkflow-handson-nodejs:main
# Pull by specific commit
docker pull ghcr.io/ekino/githubworkflow-handson-nodejs:a1b2c3dThe multi-architecture support means the same image works on both Intel/AMD machines and ARM-based systems like Apple Silicon Macs.