Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 88 additions & 15 deletions content/guides/angular/containerize.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
- Containerize an Angular application using Docker.
- Create and optimize a Dockerfile for production builds.
- Use multi-stage builds to minimize image size.
- Serve the application efficiently with a custom NGINX configuration.
- Serve the application efficiently with a custom Nginx configuration.
- Build secure and maintainable Docker images by following best practices.

---
Expand Down Expand Up @@ -77,7 +77,7 @@
| Question | Answer |
|------------------------------------------------------------|-----------------|
| What application platform does your project use? | Node |
| What version of Node do you want to use? | 23.11.0-alpine |
| What version of Node do you want to use? | 24.11.1-alpine |
| Which package manager do you want to use? | npm |
| Do you want to run "npm run build" before starting server? | yes |
| What directory is your build output to? | dist |
Expand Down Expand Up @@ -105,7 +105,7 @@
In this step, you’ll improve the Dockerfile and configuration files by following best practices:

- Use multi-stage builds to keep the final image clean and small
- Serve the app using NGINX, a fast and secure web server
- Serve the app using Nginx, a fast and secure web server
- Improve performance and security by only including what’s needed

These updates help ensure your app is easy to deploy, fast to load, and production-ready.
Expand All @@ -114,19 +114,90 @@
> A `Dockerfile` is a plain text file that contains step-by-step instructions to build a Docker image. It automates packaging your application along with its dependencies and runtime environment.
> For full details, see the [Dockerfile reference](/reference/dockerfile/).


### Step 2: Configure the Dockerfile

Copy and replace the contents of your existing `Dockerfile` with the configuration below:
Before creating a Dockerfile, you need to choose a base image. You can either use the [Node.js Official Image](https://hub.docker.com/_/node) or a Docker Hardened Image (DHI) from the [Hardened Image catalog](https://hub.docker.com/hardened-images/catalog).

Choosing DHI offers the advantage of a production-ready image that is lightweight and secure. For more information, see [Docker Hardened Images](https://docs.docker.com/dhi/).

> [!IMPORTANT]
> This guide uses a stable Node.js LTS image tag that is considered secure when the guide is written. Because new releases and security patches are published regularly, the tag shown here may no longer be the safest option when you follow the guide. Always review the latest available image tags and select a secure, up-to-date version before building or deploying your application.
>
> Official Node.js Docker Images: https://hub.docker.com/_/node

{{< tabs >}}
{{< tab name="Using Docker Hardened Images" >}}
Docker Hardened Images (DHIs) are available for Node.js in the [Docker Hardened Images catalog](https://hub.docker.com/hardened-images/catalog/dhi/node). Docker Hardened Images are freely available to everyone with no subscription required. You can pull and use them like any other Docker image after signing in to the DHI registry. For more information, see the [DHI quickstart](/dhi/get-started/) guide.

1. Sign in to the DHI registry:
```console
$ docker login dhi.io
```

2. Pull the Node.js DHI (check the catalog for available versions):
```console
$ docker pull dhi.io/node:24-alpine3.22-dev
```

In the following Dockerfile, the `FROM` instruction uses `dhi.io/node:24-alpine3.22-dev` as the base image.

```dockerfile
# =========================================
# Stage 1: Build the Angular Application
# =========================================

# Use a lightweight DHI Node.js image for building
FROM dhi.io/node:24-alpine3.22-dev AS builder

# Set the working directory inside the container
WORKDIR /app

# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json package-lock.json ./

# Install project dependencies using npm ci (ensures a clean, reproducible install)
RUN --mount=type=cache,target=/root/.npm npm ci

# Copy the rest of the application source code into the container
COPY . .

# Build the Angular application
RUN npm run build

# =========================================
# Stage 2: Prepare Nginx to Serve Static Files
# =========================================

FROM dhi.io/nginx:1.28.0-alpine3.21-dev AS runner

# Copy custom Nginx config
COPY nginx.conf /etc/nginx/nginx.conf

# Copy the static build output from the build stage to Nginx's default HTML serving directory
COPY --chown=nginx:nginx --from=builder /app/dist/*/browser /usr/share/nginx/html

# Use a non-root user for security best practices
USER nginx

# Expose port 8080 to allow HTTP traffic
# Note: The default Nginx container now listens on port 8080 instead of 80
EXPOSE 8080

# Start Nginx directly with custom config
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]
```

{{< /tab >}}
{{< tab name="Using the Docker Official Image" >}}

Now you need to create a production-ready multi-stage Dockerfile. Replace the generated Dockerfile with the following optimized configuration:

```dockerfile
# =========================================
# Stage 1: Build the Angular Application
# =========================================
ARG NODE_VERSION=24.7.0-alpine
ARG NODE_VERSION=24.11.1-alpine
ARG NGINX_VERSION=alpine3.22

# Use a lightweight Node.js image for building (customizable via ARG)
Expand All @@ -153,32 +224,34 @@

FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner

# Use a built-in non-root user for security best practices
USER nginx

# Copy custom Nginx config
COPY nginx.conf /etc/nginx/nginx.conf

# Copy the static build output from the build stage to Nginx's default HTML serving directory
COPY --chown=nginx:nginx --from=builder /app/dist/*/browser /usr/share/nginx/html

# Use a built-in non-root user for security best practices
USER nginx

# Expose port 8080 to allow HTTP traffic
# Note: The default NGINX container now listens on port 8080 instead of 80
# Note: The default Nginx container now listens on port 8080 instead of 80
EXPOSE 8080

# Start Nginx directly with custom config
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]

```

> [!NOTE]
> We are using nginx-unprivileged instead of the standard NGINX image to follow security best practices.
> We are using nginx-unprivileged instead of the standard Nginx image to follow security best practices.

Check failure on line 246 in content/guides/angular/containerize.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Terms] Use 'Nginx' instead of 'nginx'. Raw Output: {"message": "[Vale.Terms] Use 'Nginx' instead of 'nginx'.", "location": {"path": "content/guides/angular/containerize.md", "range": {"start": {"line": 246, "column": 16}}}, "severity": "ERROR"}

Check warning on line 246 in content/guides/angular/containerize.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Docker.We] Avoid using first-person plural like 'We'. Raw Output: {"message": "[Docker.We] Avoid using first-person plural like 'We'.", "location": {"path": "content/guides/angular/containerize.md", "range": {"start": {"line": 246, "column": 3}}}, "severity": "WARNING"}
> Running as a non-root user in the final image:
>- Reduces the attack surface
>- Aligns with Docker’s recommendations for container hardening
>- Helps comply with stricter security policies in production environments

{{< /tab >}}
{{< /tabs >}}

### Step 3: Configure the .dockerignore file

The `.dockerignore` file tells Docker which files and folders to exclude when building the image.
Expand Down Expand Up @@ -259,12 +332,12 @@

### Step 4: Create the `nginx.conf` file

To serve your Angular application efficiently inside the container, you’ll configure NGINX with a custom setup. This configuration is optimized for performance, browser caching, gzip compression, and support for client-side routing.
To serve your Angular application efficiently inside the container, you’ll configure Nginx with a custom setup. This configuration is optimized for performance, browser caching, gzip compression, and support for client-side routing.

Check failure on line 335 in content/guides/angular/containerize.md

View workflow job for this annotation

GitHub Actions / validate (vale)

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'gzip'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'gzip'?", "location": {"path": "content/guides/angular/containerize.md", "range": {"start": {"line": 335, "column": 179}}}, "severity": "ERROR"}

Create a file named `nginx.conf` in the root of your project directory, and add the following content:

> [!NOTE]
> To learn more about configuring NGINX, see the [official NGINX documentation](https://nginx.org/en/docs/).
> To learn more about configuring Nginx, see the [official Nginx documentation](https://nginx.org/en/docs/).


```nginx
Expand Down Expand Up @@ -345,7 +418,7 @@

The updated setup includes:

- The updated setup includes a clean, production-ready NGINX configuration tailored specifically for Angular.
- The updated setup includes a clean, production-ready Nginx configuration tailored specifically for Angular.
- Efficient multi-stage Docker build, ensuring a small and secure final image.

After completing the previous steps, your project directory should now contain the following files:
Expand Down
2 changes: 1 addition & 1 deletion content/guides/angular/develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Create a file named `Dockerfile.dev` in your project root with the following con
# =========================================

# Define the Node.js version to use (Alpine for a small footprint)
ARG NODE_VERSION=24.7.0-alpine
ARG NODE_VERSION=24.11.1-alpine

# Set the base image for development
FROM node:${NODE_VERSION} AS dev
Expand Down
Loading