mirror of
https://github.com/docker/docs.git
synced 2026-06-19 07:35:16 +00:00
guides: refresh nodejs (#25319)
<!--Delete sections as needed --> ## Description Refreshed Node.js guide - Updated all examples to DHI. DHI Community is now free so the DOI fallback is no longer needed. - Replaced the git clone pattern with the file scaffolding component. - Simplified the sample app to a Node.js backend API. Added links at the start of the guide to dedicated frontend framework guides. - Added "Secure your Node.js image supply chain" topic to showcase DHI. - Refreshed topic intros and related links. https://deploy-preview-25319--docsdocker.netlify.app/guides/nodejs/ ## Related issues or tickets ENGDOCS-3319 Closes #25280 ## Reviews <!-- Notes for reviewers here --> <!-- List applicable reviews (optionally @tag reviewers) --> - [ ] Technical review - [ ] Editorial review - [ ] Product review Signed-off-by: Craig Osterhout <craig.osterhout@docker.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Node.js language-specific guide
|
||||
linkTitle: Node.js
|
||||
description: Containerize and develop Node.js apps using Docker
|
||||
description: Containerize and develop Node.js applications using Docker
|
||||
keywords: getting started, node, node.js
|
||||
summary: |
|
||||
This guide explains how to containerize Node.js applications using Docker.
|
||||
@@ -16,41 +16,29 @@ params:
|
||||
time: 20 minutes
|
||||
---
|
||||
|
||||
[Node.js](https://nodejs.org/en) is a JavaScript runtime for building web applications. This guide shows you how to containerize a TypeScript Node.js application with a React frontend and PostgreSQL database.
|
||||
|
||||
The sample application is a modern full-stack Todo application featuring:
|
||||
|
||||
- **Backend**: Express.js with TypeScript, PostgreSQL database, and RESTful API
|
||||
- **Frontend**: React.js with Vite and Tailwind CSS 4
|
||||
[Node.js](https://nodejs.org/en) is a JavaScript runtime for building server-side applications. This guide shows you how to containerize a TypeScript Node.js application using Docker, starting from a simple Express API and progressively adding features like a database and CI/CD.
|
||||
|
||||
This guide focuses on a backend Node.js API. If you're building a standalone frontend application, Docker has dedicated guides for [React.js](/guides/reactjs/), [Vue.js](/guides/vuejs/), [Angular](/guides/angular/), and [Next.js](/guides/nextjs/).
|
||||
|
||||
> **Acknowledgment**
|
||||
>
|
||||
> Docker extends its sincere gratitude to [Kristiyan Velkov](https://www.linkedin.com/in/kristiyan-velkov-763130b3/) for authoring this guide. As a Docker Captain and experienced Full-stack engineer, his expertise in Docker, DevOps, and modern web development has made this resource invaluable for the community, helping developers navigate and optimize their Docker workflows.
|
||||
|
||||
---
|
||||
> Docker thanks [Kristiyan Velkov](https://www.linkedin.com/in/kristiyan-velkov-763130b3/) for his contribution to this guide.
|
||||
|
||||
## What will you learn?
|
||||
|
||||
In this guide, you will learn how to:
|
||||
In this guide, you'll learn how to:
|
||||
|
||||
- Containerize and run a Node.js application using Docker.
|
||||
- Set up a local development environment using containers.
|
||||
- Run tests inside a Docker container.
|
||||
- Set up a development container environment.
|
||||
- Configure GitHub Actions for CI/CD with Docker.
|
||||
- Deploy your Dockerized Node.js app to Kubernetes.
|
||||
- Inspect and generate supply chain attestations for your image.
|
||||
- Deploy your containerized Node.js application to Kubernetes.
|
||||
|
||||
To begin, you’ll start by containerizing an existing Node.js application.
|
||||
|
||||
---
|
||||
Start by containerizing a Node.js application.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, make sure you're familiar with the following:
|
||||
|
||||
- Basic understanding of [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) and [TypeScript](https://www.typescriptlang.org/).
|
||||
- Basic knowledge of [Node.js](https://nodejs.org/en), [npm](https://docs.npmjs.com/about-npm), and [React](https://react.dev/) for modern web development.
|
||||
- Understanding of Docker concepts such as images, containers, and Dockerfiles. If you're new to Docker, start with the [Docker basics](/get-started/docker-concepts/the-basics/what-is-a-container.md) guide.
|
||||
- Familiarity with [Express.js](https://expressjs.com/) for backend API development.
|
||||
|
||||
Once you've completed the Node.js getting started modules, you’ll be ready to containerize your own Node.js application using the examples and instructions provided in this guide.
|
||||
- Basic knowledge of [Node.js](https://nodejs.org/en) and [npm](https://docs.npmjs.com/about-npm).
|
||||
- Familiarity with Docker concepts such as images, containers, and Dockerfiles. If you're new to Docker, start with the [Docker basics](/get-started/docker-concepts/the-basics/what-is-a-container.md) guide.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Automate your builds with GitHub Actions
|
||||
linkTitle: GitHub Actions CI
|
||||
weight: 50
|
||||
weight: 40
|
||||
keywords: CI/CD, GitHub Actions, Node.js, Docker
|
||||
description: Learn how to configure CI/CD using GitHub Actions for your Node.js application.
|
||||
aliases:
|
||||
@@ -11,334 +11,125 @@ aliases:
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Complete all the previous sections of this guide, starting with [Containerize a Node.js application](containerize.md).
|
||||
Complete all the previous sections of this guide, starting with [Containerize a Node.js application](containerize.md). You must have a [GitHub](https://github.com/signup) account and a verified [Docker](https://hub.docker.com/signup) account to complete this section.
|
||||
|
||||
You must also have:
|
||||
If you haven't created a [GitHub repository](https://github.com/new) for your project yet, do that now. After creating the repository, [add a remote](https://docs.github.com/en/get-started/getting-started-with-git/managing-remote-repositories) and make sure you can commit and [push your code](https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository#about-git-push) to GitHub.
|
||||
|
||||
- A [GitHub](https://github.com/signup) account.
|
||||
- A verified [Docker Hub](https://hub.docker.com/signup) account.
|
||||
1. In your project's GitHub repository, open **Settings**, and go to **Secrets and variables** > **Actions**.
|
||||
|
||||
---
|
||||
2. Under the **Variables** tab, create a new **Repository variable** named `DOCKER_USERNAME` with your Docker ID as the value.
|
||||
|
||||
3. Create a new [Personal Access Token (PAT)](/manuals/security/access-tokens.md#create-an-access-token) for Docker Hub. You can name this token `docker-tutorial`. Make sure access permissions include Read and Write.
|
||||
|
||||
4. Add the PAT as a **Repository secret** in your GitHub repository, with the name `DOCKERHUB_TOKEN`.
|
||||
|
||||
## Overview
|
||||
|
||||
In this section, you'll set up a CI/CD pipeline using [GitHub Actions](https://docs.github.com/en/actions) to automatically:
|
||||
GitHub Actions is a CI/CD automation tool built into GitHub. A workflow is a YAML file that tells GitHub which jobs to run when something happens in your repository, like a push to a branch or a pull request opening. Workflows live in the `.github/workflows/` directory of your repository.
|
||||
|
||||
- Build your Node.js application inside a Docker container.
|
||||
- Run unit and integration tests, and make sure your application meets solid code quality standards.
|
||||
- Perform security scanning and vulnerability assessment.
|
||||
- Push production-ready images to [Docker Hub](https://hub.docker.com).
|
||||
In this section, you'll add a workflow that runs your tests on every push to the main branch, then builds your Docker image and pushes it to Docker Hub.
|
||||
|
||||
---
|
||||
## Define the GitHub Actions workflow
|
||||
|
||||
## Connect your GitHub repository to Docker Hub
|
||||
You can create a GitHub Actions workflow by creating a YAML file in the `.github/workflows/` directory of your repository. Use your favorite text editor or the GitHub web interface.
|
||||
|
||||
To enable GitHub Actions to build and push Docker images, you'll securely store your Docker Hub credentials in your new GitHub repository.
|
||||
If you prefer to use the GitHub web interface:
|
||||
|
||||
### Step 1: Connect your GitHub repository to Docker Hub
|
||||
1. Go to your repository on GitHub and select the **Actions** tab.
|
||||
|
||||
1. Create a Personal Access Token (PAT) from [Docker Hub](https://hub.docker.com).
|
||||
1. From your Docker Hub account, go to **Account Settings → Security**.
|
||||
2. Generate a new Access Token with **Read/Write** permissions.
|
||||
3. Name it something like `docker-nodejs-sample`.
|
||||
4. Copy and save the token — you'll need it in Step 4.
|
||||
2. Select **set up a workflow yourself**.
|
||||
|
||||
2. Create a repository in [Docker Hub](https://hub.docker.com/repositories/).
|
||||
1. From your Docker Hub account, select **Create a repository**.
|
||||
2. For the Repository Name, use something descriptive — for example: `nodejs-sample`.
|
||||
3. Once created, copy and save the repository name — you'll need it in Step 4.
|
||||
This takes you to a page for creating a new GitHub Actions workflow file in your repository. By default, the file is created under `.github/workflows/main.yml`. Change the filename to `build.yml`.
|
||||
|
||||
3. Create a new [GitHub repository](https://github.com/new) for your Node.js project.
|
||||
If you prefer to use your text editor, create a new file named `build.yml` in the `.github/workflows/` directory of your repository.
|
||||
|
||||
4. Add Docker Hub credentials as GitHub repository secrets.
|
||||
Add the following content to the file:
|
||||
|
||||
In your newly created GitHub repository:
|
||||
1. From **Settings**, go to **Secrets and variables → Actions → New repository secret**.
|
||||
|
||||
2. Add the following secrets:
|
||||
|
||||
| Name | Value |
|
||||
| ------------------------ | ------------------------------------------------ |
|
||||
| `DOCKER_USERNAME` | Your Docker Hub username |
|
||||
| `DOCKERHUB_TOKEN` | Your Docker Hub access token (created in Step 1) |
|
||||
| `DOCKERHUB_PROJECT_NAME` | Your Docker Project Name (created in Step 2) |
|
||||
|
||||
These secrets let GitHub Actions to authenticate securely with Docker Hub during automated workflows.
|
||||
|
||||
5. Connect your local project to GitHub.
|
||||
|
||||
Link your local project `docker-nodejs-sample` to the GitHub repository you just created by running the following command from your project root:
|
||||
|
||||
```console
|
||||
$ git remote set-url origin https://github.com/{your-username}/{your-repository-name}.git
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Replace `{your-username}` and `{your-repository}` with your actual GitHub username and repository name.
|
||||
|
||||
To confirm that your local project is correctly connected to the remote GitHub repository, run:
|
||||
|
||||
```console
|
||||
$ git remote -v
|
||||
```
|
||||
|
||||
You should see output similar to:
|
||||
|
||||
```console
|
||||
origin https://github.com/{your-username}/{your-repository-name}.git (fetch)
|
||||
origin https://github.com/{your-username}/{your-repository-name}.git (push)
|
||||
```
|
||||
|
||||
This confirms that your local repository is properly linked and ready to push your source code to GitHub.
|
||||
|
||||
6. Push your source code to GitHub.
|
||||
|
||||
Follow these steps to commit and push your local project to your GitHub repository:
|
||||
1. Stage all files for commit.
|
||||
|
||||
```console
|
||||
$ git add -A
|
||||
```
|
||||
|
||||
This command stages all changes — including new, modified, and deleted files — preparing them for commit.
|
||||
|
||||
2. Commit your changes.
|
||||
|
||||
```console
|
||||
$ git commit -m "Initial commit with CI/CD pipeline"
|
||||
```
|
||||
|
||||
This command creates a commit that snapshots the staged changes with a descriptive message.
|
||||
|
||||
3. Push the code to the `main` branch.
|
||||
|
||||
```console
|
||||
$ git push -u origin main
|
||||
```
|
||||
|
||||
This command pushes your local commits to the `main` branch of the remote GitHub repository and sets the upstream branch.
|
||||
|
||||
Once completed, your code will be available on GitHub, and any GitHub Actions workflow you've configured will run automatically.
|
||||
|
||||
> [!NOTE]
|
||||
> Learn more about the Git commands used in this step:
|
||||
>
|
||||
> - [Git add](https://git-scm.com/docs/git-add) – Stage changes (new, modified, deleted) for commit
|
||||
> - [Git commit](https://git-scm.com/docs/git-commit) – Save a snapshot of your staged changes
|
||||
> - [Git push](https://git-scm.com/docs/git-push) – Upload local commits to your GitHub repository
|
||||
> - [Git remote](https://git-scm.com/docs/git-remote) – View and manage remote repository URLs
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Set up the workflow
|
||||
|
||||
Now you'll create a GitHub Actions workflow that builds your Docker image, runs tests, and pushes the image to Docker Hub.
|
||||
|
||||
1. From your repository on GitHub, select the **Actions** tab in the top menu.
|
||||
|
||||
2. When prompted, select **Set up a workflow yourself**.
|
||||
|
||||
This opens an inline editor to create a new workflow file. By default, it will be saved to:
|
||||
`.github/workflows/main.yml`
|
||||
|
||||
3. Add the following workflow configuration to the new file:
|
||||
{{< files name="nodejs-docker-example" >}}
|
||||
|
||||
{{< file path=".github/workflows/build.yml" status="new" >}}
|
||||
```yaml
|
||||
name: CI/CD – Node.js Application with Docker
|
||||
# GitHub Actions workflow that runs on every push to main.
|
||||
# - test: runs Vitest unit tests inside a container.
|
||||
# - build_and_push: signs in to Docker Hub and the DHI registry, then
|
||||
# builds and pushes the image.
|
||||
name: Build and push Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Node.js Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
env:
|
||||
POSTGRES_DB: todoapp_test
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@{{% param "checkout_action_version" %}}
|
||||
- uses: actions/checkout@{{% param "checkout_action_version" %}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@{{% param "setup_buildx_action_version" %}}
|
||||
- name: Run tests
|
||||
run: docker build --target test -t nodejs-test . && docker run --rm nodejs-test
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@{{% param "cache_action_version" %}}
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Build test image
|
||||
uses: docker/build-push-action@{{% param "build_push_action_version" %}}
|
||||
with:
|
||||
context: .
|
||||
target: test
|
||||
tags: nodejs-app-test:latest
|
||||
platforms: linux/amd64
|
||||
load: true
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
|
||||
|
||||
- name: Run tests inside container
|
||||
run: |
|
||||
docker run --rm \
|
||||
--network host \
|
||||
-e NODE_ENV=test \
|
||||
-e POSTGRES_HOST=localhost \
|
||||
-e POSTGRES_PORT=5432 \
|
||||
-e POSTGRES_DB=todoapp_test \
|
||||
-e POSTGRES_USER=postgres \
|
||||
-e POSTGRES_PASSWORD=postgres \
|
||||
nodejs-app-test:latest
|
||||
env:
|
||||
CI: true
|
||||
timeout-minutes: 10
|
||||
|
||||
build-and-push:
|
||||
name: Build and Push Docker Image
|
||||
build_and_push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@{{% param "checkout_action_version" %}}
|
||||
- uses: actions/checkout@{{% param "checkout_action_version" %}}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@{{% param "login_action_version" %}}
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hardened Images
|
||||
uses: docker/login-action@{{% param "login_action_version" %}}
|
||||
with:
|
||||
registry: dhi.io
|
||||
username: ${{ vars.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@{{% param "setup_buildx_action_version" %}}
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@{{% param "cache_action_version" %}}
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}-buildx-
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
run: |
|
||||
echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> "$GITHUB_OUTPUT"
|
||||
echo "SHORT_SHA=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@{{% param "login_action_version" %}}
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push multi-arch production image
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@{{% param "build_push_action_version" %}}
|
||||
with:
|
||||
context: .
|
||||
target: production
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKERHUB_PROJECT_NAME }}:latest
|
||||
${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKERHUB_PROJECT_NAME }}:${{ steps.meta.outputs.SHORT_SHA }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
|
||||
tags: ${{ vars.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
|
||||
```
|
||||
{{< /file >}}
|
||||
|
||||
This workflow performs the following tasks for your Node.js application:
|
||||
{{< /files >}}
|
||||
|
||||
- Triggers on every `push` or `pull request` to the `main` branch.
|
||||
- Builds a test Docker image using the `test` stage.
|
||||
- Runs tests in a containerized environment.
|
||||
- Stops the workflow if any test fails.
|
||||
- Caches Docker build layers and npm dependencies for faster runs.
|
||||
- Authenticates with Docker Hub using GitHub secrets.
|
||||
- Builds an image using the `production` stage.
|
||||
- Tags and pushes the image to Docker Hub with `latest` and short SHA tags.
|
||||
The workflow has two jobs:
|
||||
|
||||
> [!NOTE]
|
||||
> For more information about `docker/build-push-action`, refer to the [GitHub Action README](https://github.com/docker/build-push-action/blob/master/README.md).
|
||||
1. **test**: Builds the `test` stage of the Dockerfile and runs it. If tests fail, the workflow stops and `build_and_push` doesn't run.
|
||||
2. **build_and_push**: Signs in to Docker Hub and the DHI registry, then builds and pushes the image.
|
||||
|
||||
---
|
||||
## Run the workflow
|
||||
|
||||
### Step 3: Run the workflow
|
||||
Commit the changes and push them to the `main` branch. This workflow runs every time you push changes to `main`. You can find more information about workflow triggers in the [GitHub documentation](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows).
|
||||
|
||||
After adding your workflow file, trigger the CI/CD process.
|
||||
Go to the **Actions** tab of your GitHub repository. It displays the workflow. Selecting the workflow shows you the breakdown of all the steps.
|
||||
|
||||
1. Commit and push your workflow file
|
||||
|
||||
From the GitHub editor, select **Commit changes…**.
|
||||
- This push automatically triggers the GitHub Actions pipeline.
|
||||
|
||||
2. Monitor the workflow execution
|
||||
1. From your GitHub repository, go to the **Actions** tab.
|
||||
2. Select the workflow run to follow each step: `test`, `build`, `security`, and (if successful) `push` and `deploy`.
|
||||
|
||||
3. Verify the Docker image on Docker Hub
|
||||
- After a successful workflow run, visit your [Docker Hub repositories](https://hub.docker.com/repositories).
|
||||
- You should see a new image under your repository with:
|
||||
- Repository name: `${your-repository-name}`
|
||||
- Tags include:
|
||||
- `latest` – represents the most recent successful build; ideal for quick testing or deployment.
|
||||
- `<short-sha>` – a unique identifier based on the commit hash, useful for version tracking, rollbacks, and traceability.
|
||||
|
||||
> [!TIP] Protect your main branch
|
||||
> To maintain code quality and prevent accidental direct pushes, enable branch protection rules:
|
||||
>
|
||||
> - From your GitHub repository, go to **Settings → Branches**.
|
||||
> - Under Branch protection rules, select **Add rule**.
|
||||
> - Specify `main` as the branch name.
|
||||
> - Enable options like:
|
||||
> - _Require a pull request before merging_.
|
||||
> - _Require status checks to pass before merging_.
|
||||
>
|
||||
> This ensures that only tested and reviewed code is merged into `main` branch.
|
||||
|
||||
---
|
||||
When the workflow is complete, go to your [repositories on Docker Hub](https://hub.docker.com/repositories). If you see the new repository in that list, the GitHub Actions workflow successfully pushed the image to Docker Hub.
|
||||
|
||||
## Summary
|
||||
|
||||
In this section, you set up a comprehensive CI/CD pipeline for your containerized Node.js application using GitHub Actions.
|
||||
In this section, you learned how to set up a GitHub Actions workflow for your Node.js application that includes:
|
||||
|
||||
What you accomplished:
|
||||
- Running Vitest unit tests inside a container
|
||||
- Building and pushing Docker images
|
||||
|
||||
- Created a new GitHub repository specifically for your project.
|
||||
- Generated a Docker Hub access token and added it as a GitHub secret.
|
||||
- Created a GitHub Actions workflow that:
|
||||
- Builds your application in a Docker container.
|
||||
- Run tests in a containerized environment.
|
||||
- Pushes an image to Docker Hub if tests pass.
|
||||
- Verified the workflow runs successfully.
|
||||
Related information:
|
||||
|
||||
Your Node.js application now has automated testing and deployment.
|
||||
|
||||
---
|
||||
|
||||
## Related resources
|
||||
|
||||
Deepen your understanding of automation and best practices for containerized apps:
|
||||
|
||||
- [Introduction to GitHub Actions](/guides/gha.md) – Learn how GitHub Actions automate your workflows
|
||||
- [Docker Build GitHub Actions](/manuals/build/ci/github-actions/_index.md) – Set up container builds with GitHub Actions
|
||||
- [Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) – Full reference for writing GitHub workflows
|
||||
- [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) – Learn about GHCR features and usage
|
||||
- [Best practices for writing Dockerfiles](/develop/develop-images/dockerfile_best-practices/) – Optimize your image for performance and security
|
||||
|
||||
---
|
||||
- [Introduction to GitHub Actions](/guides/gha.md)
|
||||
- [Docker Build GitHub Actions](/manuals/build/ci/github-actions/_index.md)
|
||||
- [docker/login-action](https://github.com/docker/login-action)
|
||||
- [docker/build-push-action](https://github.com/docker/build-push-action)
|
||||
- [Create a Docker Hub access token](/manuals/security/access-tokens.md#create-an-access-token)
|
||||
|
||||
## Next steps
|
||||
|
||||
Next, learn how you can deploy your containerized Node.js application to Kubernetes with production-ready configuration. This helps you ensure your application behaves as expected in a production-like environment, reducing surprises during deployment.
|
||||
In the next section, you'll learn how to inspect and generate supply chain
|
||||
attestations for your image. See [Secure your supply chain](secure-supply-chain.md).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+92
-412
@@ -3,7 +3,7 @@ title: Deploy your Node.js application
|
||||
linkTitle: Deploy your app
|
||||
weight: 50
|
||||
keywords: deploy, kubernetes, node, node.js, production
|
||||
description: Learn how to deploy your containerized Node.js application to Kubernetes with production-ready configuration
|
||||
description: Learn how to deploy your containerized Node.js application to Kubernetes.
|
||||
aliases:
|
||||
- /language/nodejs/deploy/
|
||||
- /guides/language/nodejs/deploy/
|
||||
@@ -16,143 +16,88 @@ aliases:
|
||||
|
||||
## Overview
|
||||
|
||||
In this section, you'll learn how to deploy your containerized Node.js application to Kubernetes using Docker Desktop. This deployment uses production-ready configurations including security hardening, auto-scaling, persistent storage, and high availability features.
|
||||
In this section, you'll deploy your containerized Node.js application to a local Kubernetes cluster using Docker Desktop. You'll create a Kubernetes manifest that describes how the application should run, including the application deployment, the PostgreSQL database, and the services that connect them.
|
||||
|
||||
You'll deploy a complete stack including:
|
||||
## Create a Kubernetes manifest
|
||||
|
||||
- Node.js Todo application with 3 replicas.
|
||||
- PostgreSQL database with persistent storage.
|
||||
- Auto-scaling based on CPU and memory usage.
|
||||
- Ingress configuration for external access.
|
||||
- Security settings.
|
||||
Create a new file called `nodejs-docker-example-kubernetes.yaml` in your project root:
|
||||
|
||||
## Create a Kubernetes deployment file
|
||||
|
||||
Create a new file called `nodejs-sample-kubernetes.yaml` in your project root:
|
||||
{{< files name="nodejs-docker-example" >}}
|
||||
|
||||
{{< file path="nodejs-docker-example-kubernetes.yaml" status="new" >}}
|
||||
```yaml
|
||||
# ========================================
|
||||
# Node.js Todo App - Kubernetes Deployment
|
||||
# ========================================
|
||||
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: todoapp
|
||||
labels:
|
||||
app: todoapp
|
||||
name: nodejs-docker-example
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# ConfigMap for Application Configuration
|
||||
# ========================================
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: todoapp-config
|
||||
namespace: todoapp
|
||||
name: app-config
|
||||
namespace: nodejs-docker-example
|
||||
data:
|
||||
NODE_ENV: 'production'
|
||||
ALLOWED_ORIGINS: 'https://yourdomain.com'
|
||||
POSTGRES_HOST: 'todoapp-postgres'
|
||||
POSTGRES_PORT: '5432'
|
||||
POSTGRES_DB: 'todoapp'
|
||||
POSTGRES_USER: 'todoapp'
|
||||
POSTGRES_SERVER: 'postgres'
|
||||
POSTGRES_DB: 'example'
|
||||
POSTGRES_USER: 'postgres'
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# Secret for Database Credentials
|
||||
# ========================================
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: todoapp-secrets
|
||||
namespace: todoapp
|
||||
type: Opaque
|
||||
data:
|
||||
postgres-password: dG9kb2FwcF9wYXNzd29yZA== # base64 encoded "todoapp_password"
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# PostgreSQL PersistentVolumeClaim
|
||||
# ========================================
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: postgres-pvc
|
||||
namespace: todoapp
|
||||
namespace: nodejs-docker-example
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
storageClassName: standard
|
||||
storage: 1Gi
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# PostgreSQL Deployment
|
||||
# ========================================
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: todoapp-postgres
|
||||
namespace: todoapp
|
||||
labels:
|
||||
app: todoapp-postgres
|
||||
name: postgres
|
||||
namespace: nodejs-docker-example
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: todoapp-postgres
|
||||
app: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: todoapp-postgres
|
||||
app: postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:18-alpine
|
||||
image: dhi.io/postgres:18
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
name: postgres
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
name: app-config
|
||||
key: POSTGRES_DB
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
name: app-config
|
||||
key: POSTGRES_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: todoapp-secrets
|
||||
key: postgres-password
|
||||
name: app-secrets
|
||||
key: db-password
|
||||
volumeMounts:
|
||||
- name: postgres-storage
|
||||
mountPath: /var/lib/postgresql
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- pg_isready
|
||||
- -U
|
||||
- todoapp
|
||||
- -d
|
||||
- todoapp
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- pg_isready
|
||||
- -U
|
||||
- todoapp
|
||||
- -d
|
||||
- todoapp
|
||||
command: [pg_isready]
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
@@ -161,434 +106,169 @@ spec:
|
||||
claimName: postgres-pvc
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# PostgreSQL Service
|
||||
# ========================================
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: todoapp-postgres
|
||||
namespace: todoapp
|
||||
labels:
|
||||
app: todoapp-postgres
|
||||
name: postgres
|
||||
namespace: nodejs-docker-example
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
protocol: TCP
|
||||
name: postgres
|
||||
selector:
|
||||
app: todoapp-postgres
|
||||
app: postgres
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# Application Deployment
|
||||
# ========================================
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: todoapp-deployment
|
||||
namespace: todoapp
|
||||
labels:
|
||||
app: todoapp
|
||||
name: server
|
||||
namespace: nodejs-docker-example
|
||||
spec:
|
||||
replicas: 3
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: todoapp
|
||||
app: server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: todoapp
|
||||
app: server
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
fsGroup: 1001
|
||||
containers:
|
||||
- name: todoapp
|
||||
image: ghcr.io/your-username/docker-nodejs-sample:latest
|
||||
imagePullPolicy: Always
|
||||
- name: server
|
||||
image: DOCKER_USERNAME/nodejs-docker-example:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: http
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
- name: POSTGRES_SERVER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
key: NODE_ENV
|
||||
- name: ALLOWED_ORIGINS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
key: ALLOWED_ORIGINS
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
key: POSTGRES_PORT
|
||||
name: app-config
|
||||
key: POSTGRES_SERVER
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
name: app-config
|
||||
key: POSTGRES_DB
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: todoapp-config
|
||||
name: app-config
|
||||
key: POSTGRES_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: todoapp-secrets
|
||||
key: postgres-password
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
name: app-secrets
|
||||
key: db-password
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
memory: '256Mi'
|
||||
cpu: '250m'
|
||||
limits:
|
||||
memory: '512Mi'
|
||||
cpu: '500m'
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# Application Service
|
||||
# ========================================
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: todoapp-service
|
||||
namespace: todoapp
|
||||
labels:
|
||||
app: todoapp
|
||||
name: server
|
||||
namespace: nodejs-docker-example
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: todoapp
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# Ingress for External Access
|
||||
# ========================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: todoapp-ingress
|
||||
namespace: todoapp
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
cert-manager.io/cluster-issuer: 'letsencrypt-prod'
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- yourdomain.com
|
||||
secretName: todoapp-tls
|
||||
rules:
|
||||
- host: yourdomain.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: todoapp-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# HorizontalPodAutoscaler
|
||||
# ========================================
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: todoapp-hpa
|
||||
namespace: todoapp
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: todoapp-deployment
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
|
||||
---
|
||||
# ========================================
|
||||
# PodDisruptionBudget
|
||||
# ========================================
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: todoapp-pdb
|
||||
namespace: todoapp
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: todoapp
|
||||
app: server
|
||||
```
|
||||
{{< /file >}}
|
||||
|
||||
## Configure the deployment
|
||||
{{< /files >}}
|
||||
|
||||
Before deploying, you need to customize the deployment file for your environment:
|
||||
Before applying the manifest, replace `DOCKER_USERNAME` in the `server` deployment's image field with your Docker Hub username.
|
||||
|
||||
1. Image reference: Replace `your-username` with your GitHub username or Docker Hub username:
|
||||
## Deploy to Kubernetes
|
||||
|
||||
```yaml
|
||||
image: ghcr.io/your-username/docker-nodejs-sample:latest
|
||||
```
|
||||
|
||||
2. Domain name: Replace `yourdomain.com` with your actual domain in two places:
|
||||
|
||||
```yaml
|
||||
# In ConfigMap
|
||||
ALLOWED_ORIGINS: "https://yourdomain.com"
|
||||
|
||||
# In Ingress
|
||||
- host: yourdomain.com
|
||||
```
|
||||
|
||||
3. Database password (optional): The default password is already base64 encoded. To change it:
|
||||
|
||||
```console
|
||||
$ echo -n "your-new-password" | base64
|
||||
```
|
||||
|
||||
Then update the Secret:
|
||||
|
||||
```yaml
|
||||
data:
|
||||
postgres-password: <your-base64-encoded-password>
|
||||
```
|
||||
|
||||
4. Storage class: Adjust based on your cluster (current: `standard`)
|
||||
|
||||
## Understanding the deployment
|
||||
|
||||
The deployment file creates a complete application stack with multiple components working together.
|
||||
|
||||
### Architecture
|
||||
|
||||
The deployment includes:
|
||||
|
||||
- Node.js application: Runs 3 replicas of your containerized Todo app
|
||||
- PostgreSQL database: Single instance with 10Gi of persistent storage
|
||||
- Services: Kubernetes services handle load balancing across application replicas
|
||||
- Ingress: External access through an ingress controller with SSL/TLS support
|
||||
|
||||
### Security
|
||||
|
||||
The deployment uses several security features:
|
||||
|
||||
- Containers run as a non-root user (UID 1001)
|
||||
- Read-only root filesystem prevents unauthorized writes
|
||||
- Linux capabilities are dropped to minimize attack surface
|
||||
- Sensitive data like database passwords are stored in Kubernetes secrets
|
||||
|
||||
### High availability
|
||||
|
||||
To keep your application running reliably:
|
||||
|
||||
- Three application replicas ensure service continues if one pod fails
|
||||
- Pod disruption budget maintains at least one available pod during updates
|
||||
- Rolling updates allow zero-downtime deployments
|
||||
- Health checks on the `/health` endpoint ensure only healthy pods receive traffic
|
||||
|
||||
### Auto-scaling
|
||||
|
||||
The Horizontal Pod Autoscaler scales your application based on resource usage:
|
||||
|
||||
- Scales between 1 and 5 replicas automatically
|
||||
- Triggers scaling when CPU usage exceeds 70%
|
||||
- Triggers scaling when memory usage exceeds 80%
|
||||
- Resource limits: 256Mi-512Mi memory, 250m-500m CPU per pod
|
||||
|
||||
### Data persistence
|
||||
|
||||
PostgreSQL data is stored persistently:
|
||||
|
||||
- 10Gi persistent volume stores database files
|
||||
- Database initializes automatically on first startup
|
||||
- Data persists across pod restarts and updates
|
||||
|
||||
## Deploy your application
|
||||
|
||||
### Step 1: Deploy to Kubernetes
|
||||
|
||||
Deploy your application to the local Kubernetes cluster:
|
||||
Apply the manifest to your local Kubernetes cluster:
|
||||
|
||||
```console
|
||||
$ kubectl apply -f nodejs-sample-kubernetes.yaml
|
||||
$ kubectl apply -f nodejs-docker-example-kubernetes.yaml
|
||||
```
|
||||
|
||||
You should see output confirming all resources were created:
|
||||
You should see output confirming that the resources were created:
|
||||
|
||||
```shell
|
||||
namespace/todoapp created
|
||||
secret/todoapp-secrets created
|
||||
configmap/todoapp-config created
|
||||
```console
|
||||
namespace/nodejs-docker-example created
|
||||
configmap/app-config created
|
||||
persistentvolumeclaim/postgres-pvc created
|
||||
deployment.apps/todoapp-postgres created
|
||||
service/todoapp-postgres created
|
||||
deployment.apps/todoapp-deployment created
|
||||
service/todoapp-service created
|
||||
ingress.networking.k8s.io/todoapp-ingress created
|
||||
poddisruptionbudget.policy/todoapp-pdb created
|
||||
horizontalpodautoscaler.autoscaling/todoapp-hpa created
|
||||
deployment.apps/postgres created
|
||||
service/postgres created
|
||||
deployment.apps/server created
|
||||
service/server created
|
||||
```
|
||||
|
||||
### Step 2: Verify the deployment
|
||||
|
||||
Check that your deployments are running:
|
||||
Then create the database secret from your password file:
|
||||
|
||||
```console
|
||||
$ kubectl get deployments -n todoapp
|
||||
$ kubectl create secret generic app-secrets \
|
||||
--namespace nodejs-docker-example \
|
||||
--from-file=db-password=db/password.txt
|
||||
```
|
||||
|
||||
Expected output:
|
||||
## Verify the deployment
|
||||
|
||||
```shell
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
todoapp-deployment 3/3 3 3 30s
|
||||
todoapp-postgres 1/1 1 1 30s
|
||||
```
|
||||
|
||||
Verify your services are created:
|
||||
Check that your pods are running:
|
||||
|
||||
```console
|
||||
$ kubectl get services -n todoapp
|
||||
$ kubectl get pods -n nodejs-docker-example
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```shell
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
todoapp-service ClusterIP 10.111.101.229 <none> 80/TCP 45s
|
||||
todoapp-postgres ClusterIP 10.111.102.130 <none> 5432/TCP 45s
|
||||
```
|
||||
|
||||
Check that persistent storage is working:
|
||||
Wait until all pods show `Running` in the STATUS column. Then verify your services:
|
||||
|
||||
```console
|
||||
$ kubectl get pvc -n todoapp
|
||||
$ kubectl get services -n nodejs-docker-example
|
||||
```
|
||||
|
||||
Expected output:
|
||||
## Access the application
|
||||
|
||||
```shell
|
||||
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
|
||||
postgres-pvc Bound pvc-12345678-1234-1234-1234-123456789012 10Gi RWO standard 1m
|
||||
```
|
||||
|
||||
### Step 3: Access your application
|
||||
|
||||
For local testing, use port forwarding to access your application:
|
||||
Use port forwarding to access the application from your local machine:
|
||||
|
||||
```console
|
||||
$ kubectl port-forward -n todoapp service/todoapp-service 8080:80
|
||||
$ kubectl port-forward -n nodejs-docker-example service/server 3000:3000
|
||||
```
|
||||
|
||||
Open your browser and visit [http://localhost:8080](http://localhost:8080) to see your Todo application running in Kubernetes.
|
||||
Open a new terminal and make a request to the application:
|
||||
|
||||
### Step 4: Test the deployment
|
||||
```console
|
||||
$ curl http://localhost:3000
|
||||
{"message":"Hello World"}
|
||||
```
|
||||
|
||||
Test that your application is working correctly:
|
||||
You can also create a hero:
|
||||
|
||||
1. Add some todos through the web interface
|
||||
2. Check application pods:
|
||||
```console
|
||||
$ curl -X POST http://localhost:3000/heroes/ \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name": "my hero", "secret_name": "austing", "age": 12}'
|
||||
```
|
||||
|
||||
```console
|
||||
$ kubectl get pods -n todoapp -l app=todoapp
|
||||
```
|
||||
|
||||
3. View application logs:
|
||||
|
||||
```console
|
||||
$ kubectl logs -f deployment/todoapp-deployment -n todoapp
|
||||
```
|
||||
|
||||
4. Check database connectivity:
|
||||
|
||||
```console
|
||||
$ kubectl get pods -n todoapp -l app=todoapp-postgres
|
||||
```
|
||||
|
||||
5. Monitor auto-scaling:
|
||||
```console
|
||||
$ kubectl describe hpa todoapp-hpa -n todoapp
|
||||
```
|
||||
|
||||
### Step 5: Clean up
|
||||
## Clean up
|
||||
|
||||
When you're done testing, remove the deployment:
|
||||
|
||||
```console
|
||||
$ kubectl delete -f nodejs-sample-kubernetes.yaml
|
||||
$ kubectl delete -f nodejs-docker-example-kubernetes.yaml
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
You've deployed your containerized Node.js application to Kubernetes. You learned how to:
|
||||
In this section, you deployed your containerized Node.js application to Kubernetes. You created a manifest that defines the application and database deployments, applied it to a local cluster, and verified the application is accessible.
|
||||
|
||||
- Create a comprehensive Kubernetes deployment file with security hardening
|
||||
- Deploy a multi-tier application (Node.js + PostgreSQL) with persistent storage
|
||||
- Configure auto-scaling, health checks, and high availability features
|
||||
- Test and monitor your deployment locally using Docker Desktop's Kubernetes
|
||||
Related information:
|
||||
|
||||
Your application is now running in a production-like environment with enterprise-grade features including security contexts, resource management, and automatic scaling.
|
||||
|
||||
---
|
||||
|
||||
## Related resources
|
||||
|
||||
Explore official references and best practices to sharpen your Kubernetes deployment workflow:
|
||||
|
||||
- [Kubernetes documentation](https://kubernetes.io/docs/home/) – Learn about core concepts, workloads, services, and more.
|
||||
- [Deploy on Kubernetes with Docker Desktop](/manuals/desktop/use-desktop/kubernetes.md) – Use Docker Desktop's built-in Kubernetes support for local testing and development.
|
||||
- [`kubectl` CLI reference](https://kubernetes.io/docs/reference/kubectl/) – Manage Kubernetes clusters from the command line.
|
||||
- [Kubernetes Deployment resource](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) – Understand how to manage and scale applications using Deployments.
|
||||
- [Kubernetes Service resource](https://kubernetes.io/docs/concepts/services-networking/service/) – Learn how to expose your application to internal and external traffic.
|
||||
- [Kubernetes documentation](https://kubernetes.io/docs/home/)
|
||||
- [Deploy on Kubernetes with Docker Desktop](/manuals/desktop/use-desktop/kubernetes.md)
|
||||
- [`kubectl` CLI reference](https://kubernetes.io/docs/reference/kubectl/)
|
||||
- [Kubernetes Deployment resource](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
|
||||
- [Kubernetes Service resource](https://kubernetes.io/docs/concepts/services-networking/service/)
|
||||
|
||||
+540
-433
File diff suppressed because it is too large
Load Diff
+210
-174
@@ -2,7 +2,7 @@
|
||||
title: Run Node.js tests in a container
|
||||
linkTitle: Run your tests
|
||||
weight: 30
|
||||
keywords: node.js, node, test
|
||||
keywords: node.js, node, test, vitest
|
||||
description: Learn how to run your Node.js tests in a container.
|
||||
aliases:
|
||||
- /language/nodejs/run-tests/
|
||||
@@ -15,214 +15,250 @@ Complete all the previous sections of this guide, starting with [Containerize a
|
||||
|
||||
## Overview
|
||||
|
||||
Testing is a core part of building reliable software. Whether you're writing unit tests, integration tests, or end-to-end tests, running them consistently across environments matters. Docker makes this easy by giving you the same setup locally, in CI/CD, and during image builds.
|
||||
Testing is a core part of building reliable software. Docker makes it easy to
|
||||
run your tests in the same environment used in CI and production, so failures
|
||||
are caught before they reach your users.
|
||||
|
||||
## Run tests when developing locally
|
||||
In this section, you'll add [Vitest](https://vitest.dev/) to the project and
|
||||
run tests both locally and inside a container.
|
||||
|
||||
The sample application uses Vitest for testing, and it already includes tests for React components, custom hooks, API routes, database operations, and utility functions.
|
||||
## Update the application
|
||||
|
||||
### Run tests locally (without Docker)
|
||||
You'll refactor `src/index.ts` to export the Express `app` instance so tests
|
||||
can import it without starting a server. Add a test file and update
|
||||
`package.json` to add Vitest and a test runner for HTTP requests. The file browser shows only the files that change in this step.
|
||||
|
||||
{{< files name="nodejs-docker-example" >}}
|
||||
|
||||
{{< file path="src/index.ts" status="modified" hl_lines="10,31,70-75" >}}
|
||||
```typescript
|
||||
// Express application backed by a PostgreSQL database.
|
||||
// Creates a heroes table at startup.
|
||||
// Endpoints: GET / (greeting), GET /health (health check), POST /heroes/ (create), GET /heroes/ (list).
|
||||
// See https://expressjs.com/ and https://node-postgres.com/
|
||||
|
||||
import express, { type Request, type Response } from 'express';
|
||||
import { Pool } from 'pg';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export const app = express();
|
||||
const port = parseInt(process.env.PORT ?? '3000', 10);
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
function getPassword(): string {
|
||||
const passwordFile = process.env.POSTGRES_PASSWORD_FILE;
|
||||
if (passwordFile) {
|
||||
return readFileSync(passwordFile, 'utf8').trim();
|
||||
}
|
||||
return process.env.POSTGRES_PASSWORD ?? '';
|
||||
}
|
||||
|
||||
const pool = new Pool({
|
||||
host: process.env.POSTGRES_SERVER,
|
||||
port: 5432,
|
||||
database: process.env.POSTGRES_DB,
|
||||
user: process.env.POSTGRES_USER,
|
||||
password: getPassword(),
|
||||
});
|
||||
|
||||
if (process.env.POSTGRES_SERVER) {
|
||||
pool
|
||||
.query(
|
||||
`CREATE TABLE IF NOT EXISTS heroes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
secret_name TEXT NOT NULL,
|
||||
age INTEGER
|
||||
)`,
|
||||
)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
app.get('/', (_req: Request, res: Response) => {
|
||||
res.json({ message: 'Hello World' });
|
||||
});
|
||||
|
||||
app.get('/health', (_req: Request, res: Response) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
app.post('/heroes/', async (req: Request, res: Response) => {
|
||||
const { name, secret_name, age } = req.body as {
|
||||
name: string;
|
||||
secret_name: string;
|
||||
age?: number;
|
||||
};
|
||||
const result = await pool.query(
|
||||
'INSERT INTO heroes (name, secret_name, age) VALUES ($1, $2, $3) RETURNING *',
|
||||
[name, secret_name, age],
|
||||
);
|
||||
res.json(result.rows[0]);
|
||||
});
|
||||
|
||||
app.get('/heroes/', async (_req: Request, res: Response) => {
|
||||
const result = await pool.query('SELECT * FROM heroes');
|
||||
res.json(result.rows);
|
||||
});
|
||||
|
||||
// Only start the server when this file is run directly.
|
||||
if (require.main === module) {
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening on port ${port}`);
|
||||
});
|
||||
}
|
||||
```
|
||||
{{< /file >}}
|
||||
|
||||
{{< file path="src/index.test.ts" status="new" >}}
|
||||
```typescript
|
||||
// Unit tests for the Express application.
|
||||
// Tests the root endpoint without starting a server.
|
||||
// See https://vitest.dev/ for the test framework reference.
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import { app } from './index';
|
||||
|
||||
describe('GET /', () => {
|
||||
it('returns a JSON greeting', async () => {
|
||||
const response = await request(app).get('/');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ message: 'Hello World' });
|
||||
});
|
||||
});
|
||||
```
|
||||
{{< /file >}}
|
||||
|
||||
{{< file path="package.json" status="modified" hl_lines="10,20-22" >}}
|
||||
```json
|
||||
{
|
||||
"name": "nodejs-docker-example",
|
||||
"version": "1.0.0",
|
||||
"description": "A minimal Node.js TypeScript application.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.21.2",
|
||||
"pg": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/pg": "^8.11.0",
|
||||
"supertest": "^7.0.0",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
{{< /file >}}
|
||||
|
||||
{{< /files >}}
|
||||
|
||||
## Run tests locally
|
||||
|
||||
Run the following command to run the tests locally:
|
||||
|
||||
```console
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
### Add test service to Docker Compose
|
||||
|
||||
To run tests in a containerized environment, you need to add a dedicated test service to your `compose.yml` file. Add the following service configuration:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# ... existing services ...
|
||||
|
||||
# ========================================
|
||||
# Test Service
|
||||
# ========================================
|
||||
app-test:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: test
|
||||
container_name: todoapp-test
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
POSTGRES_HOST: db
|
||||
POSTGRES_PORT: 5432
|
||||
POSTGRES_DB: todoapp_test
|
||||
POSTGRES_USER: todoapp
|
||||
POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:-todoapp_password}'
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
command: ['npm', 'run', 'test:coverage']
|
||||
networks:
|
||||
- todoapp-network
|
||||
profiles:
|
||||
- test
|
||||
```
|
||||
|
||||
This test service configuration:
|
||||
|
||||
- Builds from test stage: Uses the `test` target from your multi-stage Dockerfile
|
||||
- Isolated test database: Uses a separate `todoapp_test` database for testing
|
||||
- Profile-based: Uses the `test` profile so it only runs when explicitly requested
|
||||
- Health dependency: Waits for the database to be healthy before starting tests
|
||||
|
||||
### Run tests in a container
|
||||
|
||||
You can run tests using the dedicated test service:
|
||||
|
||||
```console
|
||||
$ docker compose up app-test --build
|
||||
```
|
||||
|
||||
Or run tests against the development service:
|
||||
|
||||
```console
|
||||
$ docker compose run --rm app-dev npm run test
|
||||
```
|
||||
|
||||
For a one-off test run with coverage:
|
||||
|
||||
```console
|
||||
$ docker compose run --rm app-dev npm run test:coverage
|
||||
```
|
||||
|
||||
### Run tests with coverage
|
||||
|
||||
To generate a coverage report:
|
||||
|
||||
```console
|
||||
$ npm run test:coverage
|
||||
$ npm install
|
||||
$ npm test
|
||||
```
|
||||
|
||||
You should see output like the following:
|
||||
|
||||
```console
|
||||
> docker-nodejs-sample@1.0.0 test
|
||||
> vitest --run
|
||||
RUN v3.0.0 /app
|
||||
|
||||
✓ src/server/__tests__/routes/todos.test.ts (5 tests) 16ms
|
||||
✓ src/shared/utils/__tests__/validation.test.ts (15 tests) 6ms
|
||||
✓ src/client/components/__tests__/LoadingSpinner.test.tsx (8 tests) 67ms
|
||||
✓ src/server/database/__tests__/postgres.test.ts (13 tests) 136ms
|
||||
✓ src/client/components/__tests__/ErrorMessage.test.tsx (8 tests) 127ms
|
||||
✓ src/client/components/__tests__/TodoList.test.tsx (8 tests) 147ms
|
||||
✓ src/client/components/__tests__/TodoItem.test.tsx (8 tests) 218ms
|
||||
✓ src/client/__tests__/App.test.tsx (13 tests) 259ms
|
||||
✓ src/client/components/__tests__/AddTodoForm.test.tsx (12 tests) 323ms
|
||||
✓ src/client/hooks/__tests__/useTodos.test.ts (11 tests) 569ms
|
||||
✓ src/index.test.ts (1)
|
||||
✓ GET / (1)
|
||||
✓ returns a JSON greeting
|
||||
|
||||
Test Files 9 passed (9)
|
||||
Tests 88 passed (88)
|
||||
Start at 20:57:19
|
||||
Duration 4.41s (transform 1.79s, setup 2.66s, collect 5.38s, tests 4.61s, environment 14.07s, prepare 4.34s)
|
||||
Test Files 1 passed (1)
|
||||
Tests 1 passed (1)
|
||||
Start at 12:00:00
|
||||
Duration 500ms
|
||||
```
|
||||
|
||||
### Test structure
|
||||
## Run tests in a container
|
||||
|
||||
The test suite covers:
|
||||
Run the tests using the dev stage of your Dockerfile:
|
||||
|
||||
- Client Components (`src/client/components/__tests__/`): React component testing with React Testing Library
|
||||
- Custom Hooks (`src/client/hooks/__tests__/`): React hooks testing with proper mocking
|
||||
- Server Routes (`src/server/__tests__/routes/`): API endpoint testing
|
||||
- Database Layer (`src/server/database/__tests__/`): PostgreSQL database operations testing
|
||||
- Utility Functions (`src/shared/utils/__tests__/`): Validation and helper function testing
|
||||
- Integration Tests (`src/client/__tests__/`): Full application integration testing
|
||||
```console
|
||||
$ docker compose run --build --rm --no-deps server npm test
|
||||
```
|
||||
|
||||
The `--no-deps` flag skips starting the database, since the unit tests don't require it. The `--rm` flag removes the container when the tests finish.
|
||||
|
||||
You should see the same test output as when running locally.
|
||||
|
||||
## Run tests when building
|
||||
|
||||
To run tests during the Docker build process, you need to add a dedicated test stage to your Dockerfile. If you haven't already added this stage, add the following to your multi-stage Dockerfile:
|
||||
To run tests during the Docker build process, add a `test` stage to your Dockerfile that runs after the dev stage.
|
||||
|
||||
```dockerfile
|
||||
# ========================================
|
||||
# Test Stage
|
||||
# ========================================
|
||||
FROM build-deps AS test
|
||||
```dockerfile {hl_lines="32-36"}
|
||||
FROM dhi.io/node:24-alpine3.23-dev AS dev
|
||||
|
||||
# Set environment
|
||||
ENV NODE_ENV=test \
|
||||
CI=true
|
||||
WORKDIR /app
|
||||
|
||||
# Copy source files
|
||||
COPY --chown=nodejs:nodejs . .
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
--mount=type=bind,source=package.json,target=package.json \
|
||||
npm install
|
||||
|
||||
# Switch to non-root user
|
||||
USER nodejs
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Run tests with coverage
|
||||
CMD ["npm", "run", "test:coverage"]
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "run", "dev"]
|
||||
|
||||
|
||||
FROM dhi.io/node:24-alpine3.23-dev AS deps
|
||||
WORKDIR /app
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
--mount=type=bind,source=package.json,target=package.json \
|
||||
npm install --omit=dev
|
||||
|
||||
FROM dhi.io/node:24-alpine3.23 AS runner
|
||||
ENV PATH=/app/node_modules/.bin:$PATH
|
||||
WORKDIR /app
|
||||
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
|
||||
COPY --from=dev --chown=node:node /app/dist ./dist
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
||||
|
||||
FROM dev AS test
|
||||
|
||||
ENV CI=true
|
||||
|
||||
CMD ["npm", "test"]
|
||||
```
|
||||
|
||||
This test stage:
|
||||
|
||||
- Test environment: Sets `NODE_ENV=test` and `CI=true` for proper test execution
|
||||
- Non-root user: Runs tests as the `nodejs` user for security
|
||||
- Flexible execution: Uses `CMD` instead of `RUN` to allow running tests during build or as a separate container
|
||||
- Coverage support: Configured to run tests with coverage reporting
|
||||
|
||||
### Build and run tests during image build
|
||||
|
||||
To build an image that runs tests during the build process, you can create a custom Dockerfile or modify the existing one temporarily:
|
||||
Then build and run the test stage:
|
||||
|
||||
```console
|
||||
$ docker build --target test -t node-docker-image-test .
|
||||
```
|
||||
|
||||
### Run tests in a dedicated test container
|
||||
|
||||
The recommended approach is to use the test service defined in `compose.yml`:
|
||||
|
||||
```console
|
||||
$ docker compose --profile test up app-test --build
|
||||
```
|
||||
|
||||
Or run it as a one-off container:
|
||||
|
||||
```console
|
||||
$ docker compose run --rm app-test
|
||||
```
|
||||
|
||||
### Run tests with coverage in CI/CD
|
||||
|
||||
For continuous integration, you can run tests with coverage:
|
||||
|
||||
```console
|
||||
$ docker build --target test --progress=plain --no-cache -t test-image .
|
||||
$ docker run --rm test-image npm run test:coverage
|
||||
```
|
||||
|
||||
You should see output containing the following:
|
||||
|
||||
```console
|
||||
✓ src/server/__tests__/routes/todos.test.ts (5 tests) 16ms
|
||||
✓ src/shared/utils/__tests__/validation.test.ts (15 tests) 6ms
|
||||
✓ src/client/components/__tests__/LoadingSpinner.test.tsx (8 tests) 67ms
|
||||
✓ src/server/database/__tests__/postgres.test.ts (13 tests) 136ms
|
||||
✓ src/client/components/__tests__/ErrorMessage.test.tsx (8 tests) 127ms
|
||||
✓ src/client/components/__tests__/TodoList.test.tsx (8 tests) 147ms
|
||||
✓ src/client/components/__tests__/TodoItem.test.tsx (8 tests) 218ms
|
||||
✓ src/client/__tests__/App.test.tsx (13 tests) 259ms
|
||||
✓ src/client/components/__tests__/AddTodoForm.test.tsx (12 tests) 323ms
|
||||
✓ src/client/hooks/__tests__/useTodos.test.ts (11 tests) 569ms
|
||||
|
||||
Test Files 9 passed (9)
|
||||
Tests 88 passed (88)
|
||||
Start at 20:57:19
|
||||
Duration 4.41s (transform 1.79s, setup 2.66s, collect 5.38s, tests 4.61s, environment 14.07s, prepare 4.34s)
|
||||
$ docker build --target test -t nodejs-app-test .
|
||||
$ docker run --rm nodejs-app-test
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
In this section, you learned how to run tests when developing locally using Docker Compose and how to run tests when building your image.
|
||||
In this section, you learned how to run tests when developing locally and inside a container.
|
||||
|
||||
Related information:
|
||||
|
||||
- [Dockerfile reference](/reference/dockerfile/) – Understand all Dockerfile instructions and syntax.
|
||||
- [Best practices for writing Dockerfiles](/develop/develop-images/dockerfile_best-practices/) – Write efficient, maintainable, and secure Dockerfiles.
|
||||
- [Compose file reference](/compose/compose-file/) – Learn the full syntax and options available for configuring services in `compose.yaml`.
|
||||
- [`docker compose run` CLI reference](/reference/cli/docker/compose/run/) – Run one-off commands in a service container.
|
||||
- [Dockerfile reference](/reference/dockerfile/)
|
||||
- [Compose file reference](/compose/compose-file/)
|
||||
- [`docker compose run` CLI reference](/reference/cli/docker/compose/run/)
|
||||
|
||||
## Next steps
|
||||
|
||||
Next, you’ll learn how to set up a CI/CD pipeline using GitHub Actions.
|
||||
In the next section, you'll learn how to set up a CI/CD pipeline using GitHub Actions.
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Secure your Node.js image supply chain
|
||||
linkTitle: Secure your supply chain
|
||||
weight: 45
|
||||
keywords: node.js, node, sbom, provenance, attestations, docker scout, supply chain, security
|
||||
description: Learn how to inspect, generate, and verify supply chain attestations for your Node.js container image.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Complete [Automate your builds with GitHub Actions](configure-github-actions.md).
|
||||
|
||||
## Overview
|
||||
|
||||
When you ship a container image, what's inside it and where it came from
|
||||
matters. Supply chain attestations are signed records that answer questions
|
||||
like which packages are in the image, what vulnerabilities affect them, how
|
||||
the image was built, and what security checks it passed.
|
||||
|
||||
In this section, you'll inspect the attestations that ship with your Docker
|
||||
Hardened Image base, generate your own SBOM and provenance attestations
|
||||
during CI, and pin the base image by digest so your builds are reproducible.
|
||||
|
||||
The inspection commands in this topic are shown manually so you can see what
|
||||
each one returns. In a real workflow you'd automate these checks with
|
||||
[Docker Scout](/scout/), which runs the same scans on every push,
|
||||
enforces policies in CI, and surfaces results in your registry and pull
|
||||
requests.
|
||||
|
||||
## Inspect the base image attestations
|
||||
|
||||
Docker Hardened Images are built to SLSA Build Level 3 and ship with a set of
|
||||
signed attestations covering bill-of-materials, vulnerabilities, build
|
||||
provenance, and security scans. See
|
||||
[DHI attestations](/manuals/dhi/core-concepts/attestations.md) for the full
|
||||
list of types and how to verify their signatures with Cosign.
|
||||
|
||||
List all the attestations available on the Node.js DHI:
|
||||
|
||||
```console
|
||||
$ docker scout attest list registry://dhi.io/node:24-alpine3.23-dev
|
||||
```
|
||||
|
||||
View the SBOM:
|
||||
|
||||
```console
|
||||
$ docker scout sbom registry://dhi.io/node:24-alpine3.23-dev
|
||||
```
|
||||
|
||||
Check known vulnerabilities:
|
||||
|
||||
```console
|
||||
$ docker scout cves registry://dhi.io/node:24-alpine3.23-dev
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The `registry://` prefix forces `docker scout` to fetch the image and its
|
||||
> attestations from the registry instead of reading a locally pulled copy. If
|
||||
> you've already pulled or built against the base image, the local copy
|
||||
> doesn't have the attached attestations, so the prefix is required to see
|
||||
> them.
|
||||
|
||||
When you base your own image on a DHI image, these attestations stay attached to the base layer in the registry. Tools that inspect your image can follow the chain back to the DHI source.
|
||||
|
||||
## Generate attestations for your image
|
||||
|
||||
Update your GitHub Actions workflow to attach SBOM and provenance attestations to the image you push.
|
||||
|
||||
Edit `.github/workflows/build.yml` and update the build-and-push step:
|
||||
|
||||
```yaml {hl_lines="6-7"}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@{{% param "build_push_action_version" %}}
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
tags: ${{ vars.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
|
||||
```
|
||||
|
||||
- `sbom: true` tells BuildKit to scan the built image and attach an SBOM attestation.
|
||||
- `provenance: mode=max` records detailed build provenance, including the source repository, commit, and build parameters.
|
||||
|
||||
The next time your workflow runs, the pushed image will carry these attestations alongside the image manifest in the registry.
|
||||
|
||||
## Inspect your pushed image's attestations
|
||||
|
||||
After your workflow pushes the image, inspect it the same way you inspected the base image:
|
||||
|
||||
```console
|
||||
$ docker scout attest list registry://DOCKER_USERNAME/REPO_NAME:latest
|
||||
$ docker scout sbom registry://DOCKER_USERNAME/REPO_NAME:latest
|
||||
```
|
||||
|
||||
The SBOM includes packages from every layer, including those inherited from `dhi.io/node:24-alpine3.23-dev`. The provenance record references the DHI base image by digest, so consumers of your image can trace the build chain back to the DHI source.
|
||||
|
||||
## Pin the base image by digest
|
||||
|
||||
Image tags like `dhi.io/node:24-alpine3.23-dev` move over time as new patches land. For reproducible builds, pin to an immutable digest.
|
||||
|
||||
Look up the digest for each image:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect dhi.io/node:24-alpine3.23-dev --format "{{ .Manifest.Digest }}"
|
||||
sha256:2bf01111c7dfe429362f64b3977f0cd6e63ff39023012f88487dec7e83aa26ca
|
||||
$ docker buildx imagetools inspect dhi.io/node:24-alpine3.23 --format "{{ .Manifest.Digest }}"
|
||||
sha256:868827fd45c6a01f7f3337ba7ff3f48ebb14da10d8cf3d347f98ded5481317a5
|
||||
```
|
||||
|
||||
Each digest is a 64-character hex string. Update your `Dockerfile` to reference each digest on its corresponding `FROM` line:
|
||||
|
||||
```dockerfile
|
||||
FROM dhi.io/node:24-alpine3.23-dev@sha256:2bf01111c7dfe429362f64b3977f0cd6e63ff39023012f88487dec7e83aa26ca AS dev
|
||||
# ...
|
||||
FROM dhi.io/node:24-alpine3.23@sha256:868827fd45c6a01f7f3337ba7ff3f48ebb14da10d8cf3d347f98ded5481317a5 AS runner
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Pinning by digest also pins you to that image's vulnerabilities. Use [Dependabot](https://docs.github.com/en/code-security/dependabot) or [Renovate](https://docs.renovatebot.com/) to automate digest updates so you get a PR when a new patched image is available, with a changelog to review before merging.
|
||||
|
||||
## Summary
|
||||
|
||||
In this section, you learned how to:
|
||||
|
||||
- Inspect the supply chain attestations that ship with the DHI base image, including SBOMs, CVE reports, and build provenance
|
||||
- Generate SBOM and provenance attestations for your own image in CI
|
||||
- Pin base images by digest for reproducible builds
|
||||
|
||||
Related information:
|
||||
|
||||
- [DHI attestations](/manuals/dhi/core-concepts/attestations.md)
|
||||
- [Verify a Docker Hardened Image](/manuals/dhi/how-to/verify.md)
|
||||
- [Docker Scout](/scout/)
|
||||
- [Build attestations](/manuals/build/metadata/attestations/_index.md)
|
||||
|
||||
## Next steps
|
||||
|
||||
In the next section, you'll deploy your application to Kubernetes.
|
||||
Reference in New Issue
Block a user