diff --git a/content/contributors/egor-chebkasov.md b/content/contributors/egor-chebkasov.md new file mode 100644 index 000000000..0338b62dd --- /dev/null +++ b/content/contributors/egor-chebkasov.md @@ -0,0 +1,12 @@ +--- +title: Egor Chebkasov +date: 2025-03-01 0:00:00 + +featured_img: /img/contributors/egor-chebkasov.jpeg + +links: + github: https://github.com/riiji + linkedin: https://www.linkedin.com/in/egor-chebkasov/ +--- + +Egor is a software dev. Loves to cook \ No newline at end of file diff --git a/content/contributors/img/egor-chebkasov.webp b/content/contributors/img/egor-chebkasov.webp new file mode 100644 index 000000000..cda0e139a Binary files /dev/null and b/content/contributors/img/egor-chebkasov.webp differ diff --git a/content/lessons/docker-better-images-nodejs/img/featured.jpg b/content/lessons/docker-better-images-nodejs/img/featured.jpg new file mode 100644 index 000000000..b2288ecaf Binary files /dev/null and b/content/lessons/docker-better-images-nodejs/img/featured.jpg differ diff --git a/content/lessons/docker-better-images-nodejs/img/featured.png b/content/lessons/docker-better-images-nodejs/img/featured.png new file mode 100644 index 000000000..89b76da93 Binary files /dev/null and b/content/lessons/docker-better-images-nodejs/img/featured.png differ diff --git a/content/lessons/docker-better-images-nodejs/img/featured.webp b/content/lessons/docker-better-images-nodejs/img/featured.webp new file mode 100644 index 000000000..dfce0535e Binary files /dev/null and b/content/lessons/docker-better-images-nodejs/img/featured.webp differ diff --git a/content/lessons/docker-better-images-nodejs/index.md b/content/lessons/docker-better-images-nodejs/index.md new file mode 100644 index 000000000..967008037 --- /dev/null +++ b/content/lessons/docker-better-images-nodejs/index.md @@ -0,0 +1,131 @@ +--- +title: Build an effective image with Node.js +lastmod: 2025-03-01T00:00:00-00:00 +publishdate: 2025-03-01T02:00:00-00:00 +author: Egor Chebkasov +draft: false +description: Learn techniques for creating efficient, secure, and more production-ready Docker images with Node.js +tags: + - docker + - node + - security + - devops + +youtube: +github: https://github.com/fireship-io/docker-nodejs-advanced-demo +# disable_toc: true +# disable_qna: true + +# courses +# step: 0 + +# versions: +# rxdart: 0.20 +--- + +Building on our [Docker Basics Tutorial](/lessons/docker-basics-tutorial-nodejs/), this guide shows you how to create better Docker images for Node.js apps. + +## The Problem with Basic Docker Images + +Let's look at a simple Dockerfile that many developers start with. It works fine for learning, but has some problems we should fix: + +{{< file "docker" "Dockerfile" >}} +```docker +FROM node:22 +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm install +COPY . . +CMD ["node", "server.js"] +``` + +This works for development, but has several issues for production: + +1. **Security vulnerabilities** - `npm install` includes dev dependencies that might have security problems +2. **Unreliable dependencies' code** - `npm install` might update packages beyond what's in package-lock.json, which can break your app +3. **Large image size** - Dev dependencies make the image much bigger than needed +4. **Huge base image** - node:22 is very large, making deployments slow and expensive + +### Base image improvements + +First, the base image is too big: + +```docker +FROM node:22 +``` + +We can use Alpine Linux instead, which is much smaller: + +```docker +FROM node:22-alpine +``` + +Google offers "distroless" images that contain only what you need to run your app - nothing else. This gives you better security. The size difference between Alpine and distroless is small so you shouldn't think about it too much. + +Please, don't overengineer. Distroless is what big companies like Google use, but for most projects, Alpine works great. Distroless images make debugging harder since they have no tools inside them. + +```docker +FROM gcr.io/distroless/nodejs22-debian12 +``` + +### npm install issues + +This line causes many problems: + +```docker +npm install +``` + +A better approach is using `npm ci`, which uses exactly what's in package-lock.json. It won't try to update packages. +Adding `--omit=dev` removes development tools like eslint and testing libraries. We only keep what the app needs to run: + +```docker +npm ci --omit=dev +``` + +Here's our improved image: + +{{< file "docker" "Dockerfile" >}} +```docker +FROM node:22-alpine +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev +COPY . . +CMD ["node", "server.js"] +``` + +Now let's try the best practice approach with distroless images. +We need a multi-stage build because distroless images don't have tools like npm: + +{{< file "docker" "Dockerfile" >}} +```docker +FROM node:22-alpine AS dependencies +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev +COPY . . + +FROM gcr.io/distroless/nodejs22-debian12 +WORKDIR /app +COPY --from=dependencies --chown=nonroot:nonroot /app . +USER nonroot +CMD ["server.js"] +``` + +### Non-root user for better security + +Notice the USER nonroot line. This is an important security practice - by default, Docker runs as root, which creates a security risk. Distroless images come with a nonroot user built-in, making it easy to follow this best practice with just one line of code. + + +## Let's look at the results + +I tested these approaches with a real Node.js app: + +| Image Type | Size | +|------------|------| +| Basic | 1.18GB | +| Alpine | 170MB | +| Distroless | 162MB | + +The difference is huge! Image size matters because your servers need to download these images. Smaller images mean faster deployments and less storage costs. \ No newline at end of file