# Getting started with the Node Chainguard Container

URL: https://chainguard-docs-preview-git-fix-learning-lab-jsonld.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/node.md
Last Modified: September 28, 2026
Tags: Chainguard Containers

Learn how to use Chainguard's Node.js container images for secure JavaScript applications with minimal vulnerabilities, distroless design, and built-in npm support


Chainguard's [Node container image](https://images.chainguard.dev/directory/image/node/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-node) provides a secure runtime for Node.js applications. This distroless image includes Node.js and npm while maintaining a minimal attack surface for production deployments.

In this guide, we'll set up a demo application and create a Dockerfile to build and execute the demo using the Node Chainguard Containers as base.

This tutorial requires Docker, Node, and Npm to be installed on your local machine.

**What is distroless?**


Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.





**What is Wolfi?**


[Wolfi](https://github.com/wolfi-dev/) is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.




**What are multi-stage builds?**


<p><a href="https://docs.docker.com/build/building/multi-stage/">Multi-stage builds</a> are a Docker feature that allow you to use multiple <code>FROM</code> statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.</p>

<p>This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.</p>

<p>Chainguard's container images are designed with this pattern in mind. Most have a <a href="/chainguard/containers/concepts/container-variants/"><code>:latest-dev</code> development variant</a> suited for use as a builder stage, and a corresponding <code>:latest</code> (or <code>-slim</code>) standard image for the distroless runtime. For example, a Go application can be compiled in the <code>go:latest-dev</code> builder stage and its binary copied into a <code>static</code> or <code>glibc-dynamic</code> runtime image — with no Go toolchain in the final container.</p>




**Chainguard Containers**


[Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.




## Step 1: Setting up a demo application

We'll start by creating a small Node application to serve as a demo. This application uses a mock server to test GET and POST requests, and is based on the [Docker Node example](https://docs.docker.com/language/nodejs/build-images/).

First, create a directory for your app. In this guide we'll use `wolfi-node`:

```shell
mkdir ~/wolfi-node && cd ~/wolfi-node
```

Run the following command to create a new `package.json` file.

```shell
npm init -y
```

Exit the `init` process.  You do not need to fill out the metadata as this is a demo.

Install `npm`:

```shell
npm install
```

Next, install the application dependencies. We'll need `ronin-server` and `ronin-mocks`, which are used together to create a "mock" server that saves JSON data in memory and returns it in subsequent GET requests to the same endpoint.

```shell
npm install ronin-server ronin-mocks
```

Using your code editor of choice, create a new file named `server.js` for your application. Here we'll use `nano`:

```shell
nano server.js
```

Copy the following code to your `server.js` file:

```js
const ronin = require('ronin-server')
const mocks = require('ronin-mocks')

const server = ronin.server()

server.use('/', mocks.server(server.Router(), false, true))
server.start()
```

Save the file when you're done. Then, run the server with:

```shell
node server.js
```

This command will start the application and wait for connections on port `8000`. The mocking server will save any JSON data submitted by a POST request

From a new terminal window, run the following command, which will make a POST request to your application sending a JSON payload:

```shell
curl --request POST \
  --url http://localhost:8000/test \
  --header 'content-type: application/json' \
  --data '{"msg": "testing" }'
```

When the connection is successful, you should get output like this on the terminal that is running the application:

```shell
2023-02-07T15:48:54:2450  INFO: POST /test
```

You can now test that the content was saved by running a GET request to the same endpoint:

```shell
curl http://localhost:8000/test
```

You'll get output similar to this:

```shell
{"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing","id":"f427f835-3e93-43ad-91c8-d150dffba0f9","createDate":"2023-02-07T14:48:54.256Z"}]}
```

The demo application is now ready. In the next step, you'll create a Dockerfile to run your app. Before moving along, make sure to stop the server running on your terminal by typing `CTRL+C` (or `CMD+C` for macOS users).

## Step 2: Creating the Dockerfile

In your code editor of choice, create a new Dockerfile:

```shell
nano Dockerfile
```

The following Dockerfile will:

1. Start a new image based on the `cgr.dev/chainguard/node:latest` container image;
2. Set the work dir to `/app` inside the container;
3. Copy application files from the current directory to the `/app` location in the container;
4. Run `npm install` to install production-only dependencies;
5. Set up additional arguments to the default entrypoint (`node`), specifying which script to run.

Copy this content to your own `Dockerfile`:

```Dockerfile
FROM cgr.dev/chainguard/node
ENV NODE_ENV=production

WORKDIR /app

COPY --chown=node:node ["package.json", "package-lock.json", "server.js", "./"]

RUN npm install --omit=dev

CMD [ "server.js" ]
```

Save the file when you're finished.

You can now build the container with:

```shell
docker build . --pull -t wolfi-node-server
```

Once the build is finished, run the container with:

```shell
docker run --rm -it -p 8000:8000 wolfi-node-server
```

And it should work the same way as before: the terminal will be blocked with the application waiting for connections on port `8000`. The difference is that this is running inside the container, so we set up a port redirect to receive requests on `localhost:8000`.

```shell
curl --request POST \
  --url http://localhost:8000/test \
  --header 'content-type: application/json' \
  --data '{"msg": "testing node wolfi image" }'
```

You can now query the same endpoint to receive the data that was stored in memory when you run the previous command:

```shell
curl http://localhost:8000/test
```

```shell
{"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing node wolfi image","id":"6011f987-b9f8-4442-8253-d54166df5966","createDate":"2023-02-07T15:57:23.520Z"}]}
```

## Advanced usage

Chainguard offers [slim variants](/chainguard/containers/concepts/container-variants/#slim-container-variants) of its Node.js container image. These have a smaller footprint than our standard Node.js images and are useful for building images with a minimal attack surface in multi-stage builds. Check out our [Node migration guide](/chainguard/containers/migration/migration-guides/node/#using-slim-images) to learn more.

<p>If your project requires a more specific set of packages that aren't included within the general-purpose Node Chainguard Container, you'll first need to check if the package you want is already available on the <a href="https://github.com/wolfi-dev/os" title="Wolfi on GitHub">wolfi-os repository</a>. </p>

<blockquote>
    <p><b>Note</b>: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with <code>apk add</code> you need to use the <code>USER root</code> directive.</p>
</blockquote>

<p>If the package is available, you can use the <a href="https://images.chainguard.dev/directory/image/wolfi-base/overview" title="Overview of wolfi-base image">wolfi-base</a> image in a Dockerfile and install what you need with <code>apk</code>, then use the resulting image as the base for your app.
For a worked example, see the <a href="/chainguard/containers/using-and-deploying/using-containers/#using-the-wolfi-base-container-image-within-dockerfiles">Using the wolfi-base container image within Dockerfiles</a> section of our <a href="/chainguard/containers/using-and-deploying/using-containers/" title="How to use Chainguard Containers">guide to using Chainguard Containers</a>.
</p>

<p>If the packages you need are not available, you can build your own apks using <a href="/open-source/build-tools/melange/">melange</a>. To get started, see <a href="/open-source/build-tools/melange/getting-started-with-melange/">Getting started with melange</a>.</p>


