02

How To Dockerize a Project

Video: Day 2/40 — How To Dockerize a Project • https://www.youtube.com/watch?v=nfRsPiRGx74 • Duration: ~35 min

Key terms

TermMeaning
DockerfileThe build recipe for an image
Base imageThe FROM image you build on top of
Build contextThe files sent to the daemon for the build
Layer cachingReuse of unchanged build steps to speed rebuilds
.dockerignoreExcludes files from the build context
CMDThe default command a container runs
ENTRYPOINTThe fixed executable for the container
TagThe name:version label on an image

Problem & solution

Knowing what a container is doesn't help until you can package your own app into one. Without a repeatable build-and-ship process, turning source code into a portable image and getting it into a registry is ad hoc and error-prone.

Solution: Write a Dockerfile (base image, copy code, install deps, set the start command), then build, tag, and push it to a registry.

The analogy

To ship cargo through a port you never hand over a loose pile of goods; you seal them into one standardized shipping container so any crane and any ship can handle it the same way. What goes inside, and how it is packed, is written on the packing manifest taped to the door, and the sealed box is then stored in a bonded warehouse until a ship needs it. Dockerizing an app is identical: the Dockerfile is the packing manifest that says how to pack your source code into one container image, which you push to an image registry.

Goal

Take a real app (here a Node.js + Express API with server.js), write a Dockerfile, build an image, run it, and push it to Docker Hub.

The Dockerize flow (ASCII)

Dockerizing an app is a four-step pipeline: write a Dockerfile, build an image, run a container, then push to a registry.

Graph legend — each node maps to a step that dockerizes the Express app:

Graph nodeMaps toWhat it does
Source code - Express server.jsthe Node.js/Express projectThe app being packaged (server.js listens on 3000)
Dockerfilethe Dockerfile belowRecipe that copies code and installs deps
Image express-api:1.0docker build -t mshaker/express-api:1.0 .The built, taggable image
Container express-apidocker run -p 3000:3000The running Express instance under test
Registry - Docker Hubdocker push mshaker/express-api:1.0Publishes the image for others / for Kubernetes to pull

Common Dockerfile instructions

These are the core instructions you combine to describe how an image is built and run.

  FROM        base image to start from
  WORKDIR     set working directory inside the image
  COPY/ADD    copy files from host into image
  RUN         execute a command at BUILD time (creates a layer)
  ENV         set environment variables
  EXPOSE      document the port the app listens on
  CMD         default command at RUN time (can be overridden)
  ENTRYPOINT  fixed command at RUN time (args appended)

Example Dockerfile (Node.js)

A typical Node.js Dockerfile installs dependencies first, then copies the source, to make the most of layer caching.

FROM node:18-alpine
WORKDIR /app

# copy manifests first to leverage layer caching
COPY package*.json ./
RUN npm install --production

# then copy the rest of the source (server.js, routes/, ...)
COPY . .

EXPOSE 3000
CMD ["node", "server.js"]

Why copy package.json first? (layer caching)

Copying manifests before the source means dependency installs are cached and only rerun when dependencies actually change.

Graph legend — each node maps to a layer in the Express Dockerfile above:

Graph nodeMaps toWhat it does
COPY . . - rebuiltthe COPY . . instructionAdds changed source (server.js), so this layer rebuilds often
RUN npm install --productionthe RUN npm install instructionInstalls deps; cached as long as package*.json is unchanged
COPY package*.json - cachedthe COPY package*.json ./ instructionBrings in manifests first so the install layer can be reused
FROM node:18-alpine - cachedthe FROM baseThe Node runtime layer everything stacks on

Build, run, push

Once the Dockerfile exists, you build the image, run it locally to test, then tag and push it to Docker Hub.

docker build -t express-api:1.0 .
docker run -d -p 3000:3000 --name express-api express-api:1.0
curl localhost:3000        # Express responds on port 3000

# tag + push to Docker Hub
docker login
docker tag express-api:1.0 mshaker/express-api:1.0
docker push mshaker/express-api:1.0

.dockerignore (don't ship junk)

A .dockerignore file excludes files from the build context, just like .gitignore does for git.

node_modules
.git
Dockerfile
*.log
.env

Keeps the build context small and avoids leaking secrets.

CMD vs ENTRYPOINT (ASCII)

CMD sets a default that arguments fully replace, while ENTRYPOINT sets a fixed command that arguments are appended to.

  CMD ["node","index.js"]          docker run img otherarg
       \__ fully replaced by "otherarg"

  ENTRYPOINT ["node"]              docker run img index.js
       \__ "index.js" appended -> node index.js

End-to-end flow

Turning your own app into a published image: write the Dockerfile, build, test locally, tag, then push.

Graph legend — each node maps to a real artifact/command for the Express app:

Graph nodeMaps toWhat it does
Source code - server.jsthe Express projectThe app to package
Dockerfilethe recipe aboveDrives docker build
Image express-api:1.0docker build -t express-api:1.0Local build artifact
Container test locallydocker run -p 3000:3000Smoke-tests the image with curl localhost:3000
mshaker/express-api:1.0docker tag outputRegistry-qualified name for pushing
Registry - Docker Hubdocker push targetStores the image for deployment

Key takeaways

  • Order Dockerfile steps from least- to most-frequently changed for caching.
  • Use .dockerignore to shrink context & protect secrets.
  • EXPOSE is documentation; -p actually publishes the port.

Checklist

  • [ ] Wrote a Dockerfile for a real app
  • [ ] Built, ran, and curled it locally
  • [ ] Pushed image to Docker Hub
  • [ ] Added a .dockerignore