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
| Term | Meaning |
|---|---|
| Dockerfile | The build recipe for an image |
| Base image | The FROM image you build on top of |
| Build context | The files sent to the daemon for the build |
| Layer caching | Reuse of unchanged build steps to speed rebuilds |
| .dockerignore | Excludes files from the build context |
| CMD | The default command a container runs |
| ENTRYPOINT | The fixed executable for the container |
| Tag | The 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 node | Maps to | What it does |
|---|---|---|
| Source code - Express server.js | the Node.js/Express project | The app being packaged (server.js listens on 3000) |
| Dockerfile | the Dockerfile below | Recipe that copies code and installs deps |
| Image express-api:1.0 | docker build -t mshaker/express-api:1.0 . | The built, taggable image |
| Container express-api | docker run -p 3000:3000 | The running Express instance under test |
| Registry - Docker Hub | docker push mshaker/express-api:1.0 | Publishes 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 node | Maps to | What it does |
|---|---|---|
| COPY . . - rebuilt | the COPY . . instruction | Adds changed source (server.js), so this layer rebuilds often |
| RUN npm install --production | the RUN npm install instruction | Installs deps; cached as long as package*.json is unchanged |
| COPY package*.json - cached | the COPY package*.json ./ instruction | Brings in manifests first so the install layer can be reused |
| FROM node:18-alpine - cached | the FROM base | The 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 node | Maps to | What it does |
|---|---|---|
| Source code - server.js | the Express project | The app to package |
| Dockerfile | the recipe above | Drives docker build |
| Image express-api:1.0 | docker build -t express-api:1.0 | Local build artifact |
| Container test locally | docker run -p 3000:3000 | Smoke-tests the image with curl localhost:3000 |
| mshaker/express-api:1.0 | docker tag output | Registry-qualified name for pushing |
| Registry - Docker Hub | docker push target | Stores the image for deployment |
Key takeaways
- Order Dockerfile steps from least- to most-frequently changed for caching.
- Use
.dockerignoreto shrink context & protect secrets. EXPOSEis documentation;-pactually 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