[Docker Series Part 5] Deploy a Static Site with Docker and nginx

한국어 버전

In Part 4 you produced static assets. The next step is to serve those files over HTTP so a browser can open them. A common beginner-friendly way to do that is nginx inside a Docker container: nginx handles web requests, and Docker packages the same setup so it runs consistently on any machine.

How this post flows

  1. Project setup and mental model
  2. The minimum nginx.conf lines you need first
  3. A basic Dockerfile using nginx:alpine
  4. A 10-minute terminal lab
  5. Common pitfalls and extension points

Reading card

  • Estimated time: 15 minutes, including the 10-minute lab
  • Prereqs: have run docker build and docker run at least once
  • After reading: you can explain the “static folder → nginx image → running container” pipeline.

Starting here instead of Part 4? That is fine. You only need a static output folder that contains index.html and related assets. Some tools call it build/, others call it dist/.

Project setup and mental model

Before reading either file, place them next to your built static assets.

my-static-site/
├── build/
│   ├── index.html
│   └── assets/
├── Dockerfile
└── nginx.conf

Now keep this flow in mind:

1. build/ folder: generated HTML/CSS/JS files
2. Docker image: packages nginx plus those files together
3. Docker container: runs nginx so the files can be served
4. Browser request: reaches nginx through the published port

nginx is the web server application. Docker is the packaging tool that bundles nginx together with your static files. The container is the running instance of that package. nginx does not care which framework produced the files. It simply serves whatever lives under /usr/share/nginx/html.

One more concept matters before the commands: port mapping. Inside the container, nginx listens on port 80. On your own machine, you will usually publish that as a different port such as 8080 with -p 8080:80.

  • left side of 8080:80: the port on your machine
  • right side of 8080:80: the port inside the container

Read the minimum nginx.conf first

The Dockerfile needs nginx.conf, so it helps to understand that file first. You do not need a giant config file. This small server block is enough for many static sites:

server {
  listen 80;
  server_name _;
  root /usr/share/nginx/html;
  location / { try_files $uri $uri/ /index.html; }
}
  • listen 80; means nginx accepts HTTP requests on port 80 inside the container.
  • server_name _; means "accept requests no matter which domain name was used." That is convenient for local testing. In production, replace _ with your real domain such as example.com.
  • root /usr/share/nginx/html; points to the folder nginx will serve.
  • try_files $uri $uri/ /index.html; checks three paths in order: an exact file, a directory index, and finally index.html.

That last fallback is important for single-page apps such as React or Vue projects. If a browser requests /about, nginx may not find a literal /about file, so it returns index.html and lets the browser-side router take over.

If you are serving a traditional multi-page site, be careful: the SPA fallback turns many missing paths into index.html responses instead of normal 404 responses. In that case, use this version instead:

location / { try_files $uri $uri/ =404; }

Review the core Dockerfile

Most frontend tools create a folder such as build/ or dist/ when you run their build command. In this post we assume your final static files are already in build/, and that folder contains index.html plus assets.

This Dockerfile copies the static files into nginx and publishes the server on port 80 inside the container:

FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY build/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
  • We use nginx:alpine because it is a small nginx image that is popular for static-site containers.
  • RUN rm -rf ... clears the default welcome page so you start from a clean document root. In newer nginx images this may already be minimal, but clearing it makes the document root explicit for learners.
  • COPY build/ /usr/share/nginx/html/ moves your build artifacts into the document root.
  • COPY nginx.conf ... replaces the default server block in /etc/nginx/conf.d/ with your settings.
  • EXPOSE 80 documents that nginx listens on port 80 inside the container. It does not publish the port by itself; you still need docker run -p 8080:80 when you start the container.

For a first local deployment, stop there. That is the true minimum.

If you want an optional health check later, add a tool such as wget first and then use a command like this:

RUN apk add --no-cache wget
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -q --spider http://127.0.0.1/ || exit 1

Here 127.0.0.1 means "inside the container itself." The health check runs from the container's point of view, not from your laptop.

Production note: This example is intentionally minimal for learning. In production you would usually add stricter security settings, review caching headers carefully, and decide whether you want nginx to run as a non-root user.

Mini lab: build an image and inspect the container

  1. Run your static site build (npm run build or similar) and confirm the build/ folder exists.
ls -la build/index.html
  1. Place the example Dockerfile and nginx.conf in the same directory as that build/ folder.
  2. Run these commands:
docker build -t my-static-site .
docker run -d --name my-static -p 8080:80 my-static-site

The second command maps port 8080 on your machine to port 80 inside the container. That is why you visit localhost:8080 instead of localhost:80.

  1. Visit http://localhost:8080 in a browser and confirm it renders.
  2. Check the container with these commands:
docker ps
docker logs my-static
curl -I localhost:8080
  • docker ps shows whether the container is still running.
  • docker logs my-static shows nginx output and startup errors.
  • curl -I localhost:8080 sends a simple HTTP request from your machine.

Tip: If the container is not working, remove it with docker rm -f my-static, tweak the Dockerfile, rebuild, and try again. Quick "change and retry" cycles matter more than pristine first attempts.

Common mistakes and quick fixes

  • Copying the wrong build path: Confirm that build/ really exists on your machine and contains index.html. Some tools use dist/ instead.
  • Port conflicts: Change the host side of -p 8080:80 to any open port, such as 8787 or 3000.
  • Single-page app fallback used on a multi-page site: If every broken URL keeps loading the home page, switch try_files $uri $uri/ /index.html; to try_files $uri $uri/ =404;.
  • Health check failures: Remember that 127.0.0.1 in the health check means "inside the container." From your host machine, test with localhost:8080.

If the page is blank or missing, run one extra check inside the container:

docker exec my-static ls /usr/share/nginx/html

If you do not see index.html there, nginx has nothing useful to serve yet.

Frequent extension points for static deployments

Once the basic deployment works, you usually add features to solve a specific problem:

  • cache headers so browsers can reuse static files instead of downloading them on every visit
  • gzip so large text assets such as HTML, CSS, and JS transfer faster
  • redirects for paths like /ko and /en when one site serves multiple locales
  • proxy rules so /api requests reach a separate backend service
  • docker compose files when one container grows into a web + API + database setup

Despite those add-ons, the principle stays the same: copy static files into /usr/share/nginx/html and expose port 80. Master that loop and you can containerize any static site with confidence.

Next post preview: bundle environments with Docker Compose

A single image does not describe an entire service. Part 6 introduces Docker Compose so you can define multi-container environments, starting with the classic web + database combo.

💬 댓글

이 글에 대한 의견을 남겨주세요