ENTRYPOINT: Defines the main executable (fixed)
CMD: Provides default arguments (overridable)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
ENTRYPOINT ["curl"]
CMD ["https://example.com"]
# Normal run
docker run myimage
→ Executes: curl https://example.com
# Override CMD
docker run myimage https://google.com
→ Executes: curl https://google.com
# ✅ Correct (Exec form)
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# ❌ Avoid (Shell form)
ENTRYPOINT nginx -g "daemon off;"
Build your application in one stage, run it in another. Only copy what you need for production.
# ========================================
# STAGE 1: BUILD
# ========================================
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ========================================
# STAGE 2: RUNTIME
# ========================================
FROM nginx:1.25-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/public /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
node_modules
.git
.env
.env.local
*.log
Dockerfile
docker-compose.yml
ENV DB_PASSWORD=secret123
# Baked into image!
docker run -e DB_PASSWORD=secret123
# Injected at runtime
Root: Superuser with unlimited permissions (dangerous!)
Non-root: Limited user with restricted access (safe!)
docker exec -it frontend ps aux
# Output:
USER PID COMMAND
root 1 nginx: master process ← Binds port 80 only
nginx 7 nginx: worker process ← Handles ALL traffic
nginx 8 nginx: worker process ← Handles ALL traffic
FROM nginx:alpine
RUN addgroup -S app && adduser -S app -G app
RUN sed -i 's/listen 80;/listen 8080;/' /etc/nginx/conf.d/default.conf
RUN chown -R app:app /var/cache/nginx /var/run /usr/share/nginx/html
USER app
EXPOSE 8080
Nginx sits between browser and backend, forwarding requests transparently
upstream backend_api {
server YOUR_BACKEND_IP:8000;
}
server {
listen 80;
root /usr/share/nginx/html;
# Frontend routes
location / {
try_files $uri $uri/ /index.html;
}
# API proxy - THE MAGIC ✨
location /api/ {
proxy_pass http://backend_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Combines: Multi-stage + Security + Nginx proxy + Proper user management
# ============================================
# STAGE 1: BUILD
# ============================================
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# STAGE 2: RUNTIME
# ============================================
FROM nginx:1.25-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/public /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# Build image
docker build -t frontend-app:v1.0 .
# Run container
docker run -d -p 80:80 --restart always frontend-app:v1.0
# Test
curl http://localhost
curl http://localhost/api/health
A to Z - Everything You Need to Know
ENTRYPOINT: Defines the main executable (fixed)
CMD: Provides default arguments (overridable)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
ENTRYPOINT ["curl"]
CMD ["https://example.com"]
# Normal run
docker run myimage
# Executes: curl https://example.com
# Override CMD
docker run myimage https://google.com
# Executes: curl https://google.com
# ✅ Correct (Exec form)
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# ❌ Avoid (Shell form)
ENTRYPOINT nginx -g "daemon off;"
Build your application in one stage, run it in another. Only copy what you need for production.
# ========================================
# STAGE 1: BUILD
# ========================================
FROM node:20-alpine AS build
WORKDIR /app
# Copy deps first (caching optimization)
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ========================================
# STAGE 2: RUNTIME
# ========================================
FROM nginx:1.25-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy ONLY build output
COPY --from=build /app/dist/public /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
node_modules
.git
.env
.env.local
*.log
Dockerfile
docker-compose.yml
ENV DB_PASSWORD=secret123
Baked into image
docker run -e DB_PASSWORD=secret123
Injected at runtime
# Using Docker Scout
docker scout cves myimage:latest
# Using Trivy
trivy image myimage:latest
Root: Superuser with unlimited permissions
Non-root: Limited user with restricted access
docker exec -it frontend ps aux
# Output:
# USER PID COMMAND
# root 1 nginx: master process ← Binds port 80
# nginx 7 nginx: worker process ← Handles traffic
# nginx 8 nginx: worker process ← Handles traffic
FROM nginx:alpine
RUN addgroup -S app && adduser -S app -G app
RUN sed -i 's/listen 80;/listen 8080;/' /etc/nginx/conf.d/default.conf
RUN chown -R app:app /var/cache/nginx /var/run /usr/share/nginx/html
USER app
EXPOSE 8080
Nginx sits between the browser and backend servers, forwarding requests transparently to the appropriate backend service.
🌐 Browser → 🔄 Nginx (Port 80) → ⚡ Backend API (Port 8000)
upstream backend_api {
server 74.234.94.110:8000;
}
server {
listen 80;
root /usr/share/nginx/html;
# Frontend routes
location / {
try_files $uri $uri/ /index.html;
}
# API proxy - THE MAGIC
location /api/ {
proxy_pass http://backend_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
http://myapp.com/api/users/api/74.234.94.110:8000upstream api_backend {
server backend1.local:8000;
}
upstream auth_backend {
server backend2.local:9000;
}
server {
listen 80;
location /api/ {
proxy_pass http://api_backend;
}
location /auth/ {
proxy_pass http://auth_backend;
}
}
Combines: multi-stage + security + nginx proxy + proper user management
# ============================================
# STAGE 1: BUILD
# ============================================
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# STAGE 2: RUNTIME
# ============================================
FROM nginx:1.25-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/public /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
project-root/
├── Dockerfile
├── nginx.conf
├── .dockerignore
├── package.json
└── src/
# Build image
docker build -t frontend-app:v1.0 .
# Run container
docker run -d -p 80:80 --restart always frontend-app:v1.0
# Test endpoints
curl http://localhost
curl http://localhost/api/health
# View logs
docker logs frontend-app
# Stop container
docker stop frontend-app
# Development
docker run -p 3000:80 -e ENV=dev myapp:v1.0
# Staging
docker run -p 80:80 -e ENV=staging myapp:v1.0
# Production
docker run -d -p 80:80 \
-e ENV=production \
-e API_URL=https://api.prod.com \
--restart always \
myapp:v1.0
A: ENTRYPOINT defines the main executable that always runs. CMD provides default arguments that can be overridden at runtime. Together, they provide flexibility with safety - you ensure the correct program runs while allowing configuration changes.
Example: With ENTRYPOINT ["curl"] and CMD ["https://example.com"], running docker run myimage executes curl https://example.com, but docker run myimage https://google.com executes curl https://google.com.
A: Multi-stage builds reduce the final image size and attack surface by separating build-time dependencies from runtime dependencies. Build tools stay in the build stage, only runtime artifacts go to production. This results in images that are ~97% smaller (25MB vs 800MB), more secure, and faster to deploy.
Key Benefits:
A: The master process runs as root only to bind privileged port 80, but all request-handling workers run as the unprivileged nginx user. This means 99.99% of traffic is handled by non-root processes, limiting the blast radius of any exploit. For maximum security, you can configure Nginx to use port 8080 and run everything as non-root.
Process Breakdown:
A: Nginx sits between the client and backend servers. When a request comes in, Nginx examines the URL path and forwards it to the appropriate backend server based on location blocks. For example, requests to /api/* go to the API server while / serves static files. The client only knows one endpoint, eliminating CORS issues. Nginx also handles SSL termination, load balancing, and caching.
Key Advantages:
A: Never hardcode secrets in Dockerfile or ENV directives. Instead, inject secrets at runtime using:
docker run -e DB_PASSWORD=secretdocker secret create db_password -kubectl create secret generic app-secretsKey principle: Image = Blueprint, Secrets = Keys (attached at runtime)
A: .dockerignore prevents sensitive files and unnecessary data from being copied into the Docker image during build. Without it, secrets in .env files, node_modules, git history, and logs could leak into your image layers. This improves security and reduces image size.
You now have complete knowledge of Docker production best practices. From ENTRYPOINT/CMD to multi-stage builds, security, and nginx proxy configuration.
✅ Multi-stage builds | ✅ Security hardening | ✅ Non-root users
✅ Reverse proxy | ✅ Production deployment | ✅ Interview ready
🐳 Docker Production Best Practices | Complete Guide 2024