
How to Dockerize Any App
When I started learning platform engineering and deployment properly, Docker was one of those things that looked scary from the outside.
There were terms like containers, images, Dockerfile, Docker Compose, ports, volumes, and I was like:
Bro, I just want my app to run properly.
But after trying it practically, Docker started making sense.
In simple words, Docker helps us package our app with everything it needs to run, so it can run the same way on any machine.
Whether the app is built with React, Next.js, Astro, Express, or anything else, the basic idea is almost the same.
In this blog, I will explain how to dockerize any app in a simple beginner-friendly way.
What Does Dockerizing An App Mean?
Dockerizing an app means creating a Docker setup for your project so it can run inside a container.
A container is like a small isolated environment where your app runs with its own dependencies, runtime, and commands.
Normally, when we run a project, we do things like:
npm install
npm run build
npm run startBut with Docker, we package this whole setup into a Docker image.
Then anyone can run the app using:
docker runor:
docker compose upThis makes the project easier to run, share, test, and deploy.
Why Do We Need Docker?
The biggest reason is consistency.
Many times, an app works perfectly on our laptop but fails on another machine or server.
This usually happens because of different:
- Node.js versions
- package manager versions
- operating systems
- missing dependencies
- environment variables
- build setup issues
This is the classic:
"It works on my machine."
Docker helps reduce this problem.
With Docker, we define the environment once, and the app runs inside that environment everywhere.
So instead of saying:
Install Node 20, install pnpm, install dependencies, setup this, setup that...
We can say:
Run this Docker container.
Much cleaner.
Basic Docker Terms You Should Know
Before writing Docker files, these are the basic terms I understood first.
Dockerfile
A Dockerfile is a file where we write instructions for building our app image.
Example:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["npm", "start"]This tells Docker how to prepare and run the app.
Image
An image is like a ready-made package of your app.
It contains your code, dependencies, runtime, and build setup.
You create an image from a Dockerfile.
Container
A container is the running version of an image.
Simple difference:
Dockerfile = instructions
Image = packaged app
Container = running appDocker Compose
Docker Compose is used when we want to run multiple things together.
For example:
- frontend app
- backend API
- PostgreSQL database
- Redis
- Nginx
Instead of running all containers manually, we define them in one docker-compose.yml file.
Then run:
docker compose upBasic Steps To Dockerize Any App
The process is mostly the same for many JavaScript apps.
Step 1: Check How Your App Runs Normally
Before Docker, first understand your project.
Check your package.json.
Example:
{
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
}
}or for Next.js:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}This tells you what command is needed to build and run the app.
Step 2: Create A .dockerignore File
This file tells Docker what not to copy into the image.
Create a file named:
.dockerignoreAdd this:
node_modules
.next
dist
build
.env
.git
.gitignore
Dockerfile
docker-compose.yml
npm-debug.logThis keeps the Docker image cleaner and smaller.
Step 3: Create A Dockerfile
Now create a file named:
DockerfileThis is where we define how the app should be built and started.
Dockerfile For A Normal React/Vite App
If your app is built with React and Vite, you can use this:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]What This Does
First, it uses Node.js to install dependencies and build the React app.
Then it uses Nginx to serve the final static files.
React/Vite apps usually generate a dist folder after build.
So we copy that dist folder into Nginx.
Dockerfile For An Astro App
Astro apps can also be static or server-rendered.
For a normal static Astro app, use this:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]This is almost the same as React/Vite because Astro also creates a dist folder for static output.
Dockerfile For A Next.js App
Next.js is a little different because it usually needs a Node.js server to run.
Use this simple Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]This works for a basic Next.js app.
Make sure your package.json has this:
{
"scripts": {
"build": "next build",
"start": "next start"
}
}Then Docker can build and start the app properly.
Better Dockerfile For Next.js Production
For a more optimized Next.js Docker setup, you can use a multi-stage build:
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["npm", "start"]This separates dependency installation, building, and running the app.
It keeps the final container cleaner.
Step 4: Build The Docker Image
After creating the Dockerfile, run this command:
docker build -t my-app .Here:
my-app = name of the Docker image
. = current folderDocker will read the Dockerfile and create an image.
Step 5: Run The Container
For React/Astro static app served with Nginx:
docker run -p 8080:80 my-appNow open:
http://localhost:8080For Next.js app:
docker run -p 3000:3000 my-appNow open:
http://localhost:3000Step 6: Use Docker Compose
Instead of writing long Docker commands, we can use Docker Compose.
Create a file:
docker-compose.ymlFor Next.js:
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .envNow run:
docker compose upTo stop:
docker compose downFor React or Astro with Nginx:
services:
app:
build: .
ports:
- "8080:80"Then run:
docker compose upWhat About Environment Variables?
If your app uses environment variables, keep them in a .env file.
Example:
DATABASE_URL=""
NEXT_PUBLIC_API_URL=""
NEXT_PUBLIC_APP_URL=""Then in Docker Compose:
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .envImportant note:
Do not push your real .env file to GitHub.
Instead, create:
.env.exampleExample:
DATABASE_URL="your_database_url_here"
NEXT_PUBLIC_API_URL="your_api_url_here"
NEXT_PUBLIC_APP_URL="your_app_url_here"This helps others understand what env variables are needed.
Common Issue: App Runs Locally But Not In Docker
This can happen because of:
- wrong port
- missing environment variables
- wrong build command
- missing files
- app binding only to localhost
- Node version mismatch
- dependency issue
The first thing I usually check is logs:
docker logs container_nameOr if using Docker Compose:
docker compose logsLogs are your best friend when debugging Docker issues.
Common Commands I Use
Build image:
docker build -t my-app .Run container:
docker run -p 3000:3000 my-appRun with Docker Compose:
docker compose upRun in background:
docker compose up -dStop containers:
docker compose downView logs:
docker compose logsRebuild:
docker compose up --buildCheck running containers:
docker psWhen Should You Dockerize An App?
You should consider Docker when:
- you want consistent setup across machines
- your app has many dependencies
- you are deploying to a VPS
- you are working with a team
- you want easier CI/CD
- you want to run frontend, backend, database together
- you want a clean production setup
For very small static sites, Docker may not always be needed.
For example, if you are deploying a simple Astro or React site to Vercel, Netlify, or Cloudflare Pages, Docker is optional.
But if you are learning deployment, DevOps, or platform engineering, Docker is definitely worth learning.
My Simple Mental Model
This is how I understand Docker now:
Without Docker:
My app depends on the machine setup.
With Docker:
My app brings its own setup.That is the main idea.
Docker is not magic.
It just makes the environment predictable.
Final Thoughts
Dockerization is not about making things complicated.
It is about making the app easier to run, easier to share, and easier to deploy.
At first, Docker feels confusing because there are new terms like images, containers, Dockerfile, and Compose.
But once you dockerize one real app, it starts making sense.
My suggestion is simple:
Pick one app you already built.
Add a Dockerfile.
Run it inside Docker.
Break it.
Fix it.
Then try Docker Compose.
That is how I am learning it too: by building, not by watching 50 tutorials and forgetting everything after two days.
Docker is one of those skills that becomes clearer only when you actually use it.