“Works on my machine” is one of the oldest problems in software. Docker solves it by packaging your app together with the exact runtime it needs, so the same thing runs on your laptop, on a teammate’s machine and on a server. For a frontend developer that mostly means one thing: building and serving your site the same way everywhere, without installing the right Node version by hand.
Images and containers
Two words cover most of Docker. An image is a read-only template that contains your code, its dependencies and the operating system layer underneath. A container is a running instance of an image. If you know classes and objects, the comparison fits: build the image once, then start as many containers from it as you like.
Your first Dockerfile
A Dockerfile is a list of steps that builds an image. This one packages a SvelteKit app that uses the Node adapter:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "build"]FROM picks the base image, here the small Alpine variant of Node. WORKDIR sets the folder everything else happens in. The order of the COPY lines matters. Copying only the package files first and running npm ci means Docker can reuse that layer on the next build as long as your dependencies did not change. Only the final COPY and the build re-run when your source code changes, and that ordering is the biggest speed-up for everyday builds.
Keep the build context small
Docker sends your project folder to the build, and anything you do not need makes that slower. A .dockerignore file works like .gitignore:
node_modules
.git
.svelte-kit
build
.envBesides speed, this keeps local secrets such as .env files out of the image, where they could be extracted later by anyone who gets the image.
Multi-stage builds
The image above still contains dev dependencies, build tools and source files you do not need at runtime. A multi-stage build fixes that by using one stage to build and a second, clean stage to run:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --omit=dev
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/build ./build
COPY --from=build /app/node_modules ./node_modules
COPY package.json .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "build"]Only the folders copied with COPY --from end up in the final image, so it stays small and has less in it that could be attacked.
Build and run
Building and starting the container takes two commands:
docker build -t my-site .
docker run --rm -p 3000:3000 my-site-t gives the image a name. In -p 3000:3000 the first number is the port on your machine and the second is the port inside the container, so the site is now available at localhost:3000. --rm removes the container when it stops, which keeps your machine tidy.
Compose when there is more than one service
Real projects often need more than the website: a CMS, a database or a cache. Docker Compose describes all of them in one file.
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stoppedStart everything with docker compose up --build. When you add a second service later, such as a headless CMS, it goes into the same file and the services can reach each other by name.
Commands you will use daily
| Command | What it does |
|---|---|
docker ps | Lists running containers |
docker logs -f name | Follows a container’s logs |
docker exec -it name sh | Opens a shell inside a container |
docker system prune | Removes unused images and containers |
Habits worth building
- Pin base image versions instead of using
latest, so a rebuild next month gives you the same result. - Run as a non-root user with
USER nodeonce the build steps are done. - Keep secrets in environment variables or a secrets manager, never inside the image.
- Rebuild regularly to pick up security fixes in the base image.
You do not need to become a Docker expert to benefit from it. A Dockerfile, a .dockerignore and a Compose file cover most of what a frontend project needs, and once they are in the repository anyone can start the project with a single command.
