Angular Dockerfile: Production Build Served by Nginx

This Angular Dockerfile builds your app on node:24-alpine and serves the compiled browser bundle from nginxinc/nginx-unprivileged:1.27-alpine. Nginx runs as a non-root user on port 8080, and no Node runtime ships in the final image. It suits client-side Angular apps without server-side rendering.

CI status
Build passing
Image size (uncompressed)
48.5 MB
Build time
18 s
Runtime image
nginxinc/nginx-unprivileged:1.27-alpine
Port
8080
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginxinc/nginx-unprivileged:1.27-alpine
COPY --from=build /app/dist/app/browser /usr/share/nginx/html
EXPOSE 8080
.dockerignore
.git
node_modules/
dist/
.angular/
.env*
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM node:24-alpine AS build

    Node is only needed to run the Angular CLI build. The stage and its node_modules are discarded afterwards.

  2. COPY package.json package-lock.json ./ RUN npm ci

    Installs exact dependency versions from the lockfile. Copying only the manifests first keeps this slow step cached across code changes.

  3. RUN npm run build

    Runs ng build, which uses the production configuration by default and writes optimized, hashed bundles to the dist folder.

  4. FROM nginxinc/nginx-unprivileged:1.27-alpine

    An official Nginx variant preconfigured to run as a non-root user. Because of that, it listens on port 8080 rather than 80.

  5. COPY --from=build /app/dist/app/browser /usr/share/nginx/html

    Recent Angular versions put the deployable files in a browser subfolder. Copying that folder makes it Nginx's document root.

  6. EXPOSE 8080

    Documents the port the unprivileged image listens on. Map it to any host port when you run the container.

Build and run

terminal
docker build -t angular-app .
docker run --rm -p 8080:8080 angular-app

Build from the workspace root, then open http://localhost:8080. Environment-specific API URLs are compiled into the bundle, so select them with Angular build configurations.

Other Angular Dockerfile variants

Serving the static build from Nginx is the simplest and fastest way to host client-side Angular. Apps using server-side rendering need a Node runtime variant that starts the SSR server instead. Verified variants appear in the table below.

VariantRuntime imageImage sizeCI
Static build served by unprivileged Nginxnginxinc/nginx-unprivileged:1.27-alpine48.5 MBPassing

Troubleshooting

COPY failed: /app/dist/app/browser: not found
Your project has a different name. Check angular.json for the output path and change the COPY source to dist/<your-project>/browser.
Refreshing a route like /settings returns 404 Not Found
Nginx looks for a real file at that path. Add a server block with try_files $uri $uri/ /index.html so Angular's router handles the URL.
Changes don't appear after deploying a new image
Browsers may cache index.html. Serve index.html with a no-cache header and let the hashed bundle files be cached long-term.

FAQ

Why not serve the app with ng serve?
ng serve is a development server with live reload and unoptimized builds. Nginx serves the production bundle faster, with less memory and no Node runtime in the image.
How do I set the API URL per environment?
Use Angular build configurations for compile-time values, or load a small JSON config file at startup that you mount into the Nginx document root.
Does this work with Angular SSR?
No. Server-side rendering needs Node to run the server bundle. This file serves only the static browser output.