Skip to main content

Docker basics for local builds and for Jenkins CI

Build an image locally:

cd path/to/project
docker build -t my-app:local .
docker run --rm -p 8080:80 my-app:local

Build & run with docker-compose:

cd path/to/project
docker-compose up --build

To integrate with Jenkins, ensure your Jenkinsfile builds the Docker image and pushes to a registry Jenkins can access (use credentials in Jenkins' credentials store).

See the sample docker-compose.yml and per-app Dockerfile templates in this folder.

Docker Comprehensive Guide

Table of Contents


Installation

Ubuntu Installation

Method 1: Official Docker Repository

# Update package index
sudo apt-get update

# Install prerequisites
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release

# Add Docker's official GPG key
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Set up the repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verify installation
sudo docker run hello-world

Method 2: Convenience Script


curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
Post-installation Steps (Ubuntu)
Bash

# Add your user to docker group
sudo usermod -aG docker $USER

# Activate changes to groups
newgrp docker

# Enable Docker to start on boot
sudo systemctl enable docker.service
sudo systemctl enable containerd.service

# Verify Docker is running
docker --version
docker compose version

Windows Installation

Requirements Windows 10 64-bit: Pro, Enterprise, or Education (Build 16299 or later) OR Windows 11 64-bit WSL 2 enabled Hardware virtualization enabled in BIOS Installation Steps Enable WSL 2


# Run in PowerShell as Administrator
wsl --install

Download Docker Desktop

Visit: https://www.docker.com/products/docker-desktop Download Docker Desktop for Windows Run the installer (Docker Desktop Installer.exe) Installation Process

Follow the installation wizard Ensure "Use WSL 2 instead of Hyper-V" is selected Restart your computer when prompted Verify Installation


docker --version
docker compose version
docker run hello-world

Post-installation (Windows)

Configure Docker to start on boot (usually default)

Settings > General > "Start Docker Desktop when you log in"

Configure WSL 2 integration

Settings > Resources > WSL Integration

Enable integration with your WSL 2 distros

macOS Installation

Requirements macOS 11 or newer (for Apple Silicon) macOS 10.15 or newer (for Intel) Installation Steps Download Docker Desktop

Visit: https://www.docker.com/products/docker-desktop Choose the appropriate version: Apple Silicon (M1/M2) Intel Chip Install Docker Desktop

Open the downloaded .dmg file Drag Docker icon to Applications folder Launch Docker from Applications First Launch

Docker will request privileged access Enter your password to proceed Wait for Docker to start Verify Installation


docker --version
docker compose version
docker run hello-world
Using Homebrew (Alternative)

Install Homebrew if not installed

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Install Docker Desktop

brew install --cask docker

Launch Docker Desktop

open /Applications/Docker.app Docker Commands Container Management

# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# Run a container
docker run <image>

# Run container in detached mode
docker run -d <image>

# Run container with name
docker run --name <container_name> <image>

# Run container with port mapping
docker run -p <host_port>:<container_port> <image>

# Run container with environment variables
docker run -e ENV_VAR=value <image>

# Run container with volume
docker run -v <host_path>:<container_path> <image>

# Run interactive container
docker run -it <image> /bin/bash

# Start a stopped container
docker start <container_id/name>

# Stop a running container
docker stop <container_id/name>

# Restart a container
docker restart <container_id/name>

# Pause a container
docker pause <container_id/name>

# Unpause a container
docker unpause <container_id/name>

# Remove a container
docker rm <container_id/name>

# Remove a running container (force)
docker rm -f <container_id/name>

# Remove all stopped containers
docker container prune

# Execute command in running container
docker exec <container_id/name> <command>

# Execute interactive shell in running container
docker exec -it <container_id/name> /bin/bash

# View container logs
docker logs <container_id/name>

# Follow container logs
docker logs -f <container_id/name>

# View last N lines of logs
docker logs --tail <number> <container_id/name>

# Inspect container
docker inspect <container_id/name>

# View container stats
docker stats <container_id/name>

# View container processes
docker top <container_id/name>

# Copy files from container to host
docker cp <container_id>:<container_path> <host_path>

# Copy files from host to container
docker cp <host_path> <container_id>:<container_path>

# Rename container
docker rename <old_name> <new_name>

# Wait for container to stop
docker wait <container_id/name>

# Attach to running container
docker attach <container_id/name>

Image Management


# List images
docker images

# List all images (including intermediate)
docker images -a

# Pull image from registry
docker pull <image>:<tag>

# Pull specific image version
docker pull <image>:1.0.0

# Push image to registry
docker push <image>:<tag>

# Build image from Dockerfile
docker build -t <image_name>:<tag> <path>

# Build with no cache
docker build --no-cache -t <image_name> .

# Build with build arguments
docker build --build-arg ARG_NAME=value -t <image_name> .

# Tag an image
docker tag <image_id> <new_name>:<tag>

# Remove an image
docker rmi <image_id/name>

# Remove image (force)
docker rmi -f <image_id/name>

# Remove unused images
docker image prune

# Remove all unused images
docker image prune -a

# Search for images
docker search <term>

# View image history
docker history <image>

# Inspect image
docker inspect <image>

# Save image to tar file
docker save <image> > image.tar
docker save -o image.tar <image>

# Load image from tar file
docker load < image.tar
docker load -i image.tar

# Export container to tar
docker export <container_id> > container.tar

# Import tar as image
docker import container.tar <image_name>:<tag>

Network Management


# List networks
docker network ls

# Create network
docker network create <network_name>

# Create network with subnet
docker network create --subnet=172.18.0.0/16 <network_name>

# Create bridge network
docker network create --driver bridge <network_name>

# Inspect network
docker network inspect <network_name>

# Connect container to network
docker network connect <network_name> <container_name>

# Disconnect container from network
docker network disconnect <network_name> <container_name>

# Remove network
docker network rm <network_name>

# Remove unused networks
docker network prune
Volume Management
Bash

# List volumes
docker volume ls

# Create volume
docker volume create <volume_name>

# Inspect volume
docker volume inspect <volume_name>

# Remove volume
docker volume rm <volume_name>

# Remove unused volumes
docker volume prune

# Remove all unused volumes
docker volume prune -a
Docker Compose Commands
Bash

# Start services
docker compose up

# Start services in detached mode
docker compose up -d

# Start specific service
docker compose up <service_name>

# Build services
docker compose build

# Build without cache
docker compose build --no-cache

# Start services and build
docker compose up --build

# Stop services
docker compose stop

# Stop and remove containers
docker compose down

# Stop and remove containers, networks, volumes
docker compose down -v

# View running services
docker compose ps

# View logs
docker compose logs

# Follow logs
docker compose logs -f

# View logs for specific service
docker compose logs <service_name>

# Execute command in service
docker compose exec <service_name> <command>

# Run one-off command
docker compose run <service_name> <command>

# Scale services
docker compose up -d --scale <service_name>=<number>

# Restart services
docker compose restart

# Pause services
docker compose pause

# Unpause services
docker compose unpause

# Validate compose file
docker compose config

# Pull service images
docker compose pull

# Push service images
docker compose push

# View processes
docker compose top

System Commands


# View Docker disk usage
docker system df

# Remove unused data
docker system prune

# Remove all unused data (including volumes)
docker system prune -a --volumes

# View Docker info
docker info

# View Docker version
docker version

# Login to registry
docker login

# Login to specific registry
docker login <registry_url>

# Logout from registry
docker logout

# View events
docker events

# View events with filter
docker events --filter 'event=start'
Registry Commands
Bash

# Login to Docker Hub
docker login

# Login with username and password
docker login -u <username> -p <password>

# Login to private registry
docker login <registry_url>

# Logout
docker logout

# Tag image for registry
docker tag <image> <registry>/<repository>:<tag>

# Push to registry
docker push <registry>/<repository>:<tag>

# Pull from registry
docker pull <registry>/<repository>:<tag>

Useful Command Combinations


# Stop all running containers
docker stop $(docker ps -q)

# Remove all containers
docker rm $(docker ps -a -q)

# Remove all images
docker rmi $(docker images -q)

# Remove dangling images
docker rmi $(docker images -f "dangling=true" -q)

# Remove exited containers
docker rm $(docker ps -a -f status=exited -q)

# View container IP address
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container_name>

# View container environment variables
docker exec <container_name> env

# Clean up everything
docker system prune -a --volumes

Dockerfile Examples Basic Dockerfile (Node.js)


# Use official Node.js runtime as base image
FROM node:18-alpine

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy application files
COPY . .

# Expose port
EXPOSE 3000

# Define environment variable
ENV NODE_ENV=production

# Run application
CMD ["node", "app.js"]
Multi-stage Build (Node.js)
Dockerfile

# Stage 1: Build
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Stage 2: Production
FROM node:18-alpine

WORKDIR /app

# Copy only necessary files from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3000

USER node

CMD ["node", "dist/main.js"]

Python Application


FROM python:3.11-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Create non-root user
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app

USER appuser

EXPOSE 8000

CMD ["python", "app.py"]

Go Application


# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Production stage
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=builder /app/main .

EXPOSE 8080

CMD ["./main"]

Java Application


# Build stage
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /app

COPY pom.xml .
RUN mvn dependency:go-offline

COPY src ./src
RUN mvn package -DskipTests

# Production stage
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

COPY --from=builder /app/target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

Nginx with Custom Config


FROM nginx:alpine

# Remove default nginx config
RUN rm /etc/nginx/conf.d/default.conf

# Copy custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d/ /etc/nginx/conf.d/

# Copy static files
COPY dist/ /usr/share/nginx/html/

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

PHP Application


FROM php:8.2-fpm-alpine

# Install dependencies
RUN apk add --no-cache \
git \
curl \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
zip \
unzip

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd pdo pdo_mysql

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

COPY composer*.json ./
RUN composer install --no-dev --optimize-autoloader

COPY . .

RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000

CMD ["php-fpm"]

WordPress


FROM wordpress:latest

# Install additional PHP extensions
RUN docker-php-ext-install exif

# Copy custom php.ini
COPY php.ini /usr/local/etc/php/conf.d/custom.ini

# Copy custom wp-config
COPY wp-config.php /var/www/html/

EXPOSE 80

CMD ["apache2-foreground"]

Docker Compose Examples Basic Web Application (Node.js + MongoDB)


version: '3.8'

services:
app:
build: .
container_name: node-app
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongo:27017/mydb
depends_on:
- mongo
volumes:
- ./:/app
- /app/node_modules
networks:
- app-network

mongo:
image: mongo:6
container_name: mongodb
restart: unless-stopped
ports:
- "27017:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=secret
volumes:
- mongo-data:/data/db
networks:
- app-network

volumes:
mongo-data:

networks:
app-network:
driver: bridge
Full Stack Application (React + Node + PostgreSQL + Redis)
YAML

version: '3.8'

services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: react-app
restart: unless-stopped
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:5000
volumes:
- ./frontend:/app
- /app/node_modules
networks:
- fullstack-network
depends_on:
- backend

backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: node-backend
restart: unless-stopped
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
- JWT_SECRET=your-secret-key
volumes:
- ./backend:/app
- /app/node_modules
depends_on:
- db
- redis
networks:
- fullstack-network

db:
image: postgres:15-alpine
container_name: postgres-db
restart: unless-stopped
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- fullstack-network

redis:
image: redis:7-alpine
container_name: redis-cache
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- fullstack-network

volumes:
postgres-data:
redis-data:

networks:
fullstack-network:
driver: bridge

WordPress with MySQL and phpMyAdmin


version: '3.8'

services:
wordpress:
image: wordpress:latest
container_name: wordpress
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
volumes:
- wordpress-data:/var/www/html
depends_on:
- db
networks:
- wp-network

db:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- db-data:/var/lib/mysql
networks:
- wp-network

phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin
restart: unless-stopped
ports:
- "8081:80"
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: rootpassword
depends_on:
- db
networks:
- wp-network

volumes:
wordpress-data:
db-data:

networks:
wp-network:
driver: bridge

Python Django with PostgreSQL and Nginx


version: '3.8'

services:
web:
build: .
container_name: django-app
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
- media_volume:/app/mediafiles
expose:
- 8000
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/django_db
- SECRET_KEY=your-secret-key
- DEBUG=False
depends_on:
- db
networks:
- django-network

db:
image: postgres:15-alpine
container_name: postgres
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=django_db
networks:
- django-network

nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- static_volume:/app/staticfiles
- media_volume:/app/mediafiles
depends_on:
- web
networks:
- django-network

volumes:
postgres_data:
static_volume:
media_volume:

networks:
django-network:
driver: bridge

Microservices Architecture


version: '3.8'

services:
api-gateway:
build: ./api-gateway
container_name: api-gateway
restart: unless-stopped
ports:
- "8000:8000"
environment:
- USER_SERVICE_URL=http://user-service:3001
- PRODUCT_SERVICE_URL=http://product-service:3002
- ORDER_SERVICE_URL=http://order-service:3003
networks:
- microservices-network
depends_on:
- user-service
- product-service
- order-service

user-service:
build: ./user-service
container_name: user-service
restart: unless-stopped
expose:
- 3001
environment:
- DB_HOST=user-db
- DB_PORT=5432
- DB_NAME=users
- DB_USER=postgres
- DB_PASSWORD=password
depends_on:
- user-db
- rabbitmq
networks:
- microservices-network

product-service:
build: ./product-service
container_name: product-service
restart: unless-stopped
expose:
- 3002
environment:
- DB_HOST=product-db
- DB_PORT=5432
- DB_NAME=products
- DB_USER=postgres
- DB_PASSWORD=password
depends_on:
- product-db
- rabbitmq
networks:
- microservices-network

order-service:
build: ./order-service
container_name: order-service
restart: unless-stopped
expose:
- 3003
environment:
- DB_HOST=order-db
- DB_PORT=5432
- DB_NAME=orders
- DB_USER=postgres
- DB_PASSWORD=password
depends_on:
- order-db
- rabbitmq
networks:
- microservices-network

user-db:
image: postgres:15-alpine
container_name: user-db
environment:
- POSTGRES_DB=users
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- user-db-data:/var/lib/postgresql/data
networks:
- microservices-network

product-db:
image: postgres:15-alpine
container_name: product-db
environment:
- POSTGRES_DB=products
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- product-db-data:/var/lib/postgresql/data
networks:
- microservices-network

order-db:
image: postgres:15-alpine
container_name: order-db
environment:
- POSTGRES_DB=orders
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- order-db-data:/var/lib/postgresql/data
networks:
- microservices-network

rabbitmq:
image: rabbitmq:3-management-alpine
container_name: rabbitmq
restart: unless-stopped
ports:
- "5672:5672"
- "15672:15672"
environment:
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- microservices-network

volumes:
user-db-data:
product-db-data:
order-db-data:
rabbitmq-data:

networks:
microservices-network:
driver: bridge

Development Environment (LAMP Stack)


version: '3.8'

services:
apache:
image: php:8.2-apache
container_name: apache-php
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./www:/var/www/html
depends_on:
- mysql
networks:
- lamp-network

mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- mysql-data:/var/lib/mysql
networks:
- lamp-network

phpmyadmin:
image: phpmyadmin:latest
container_name: phpmyadmin
restart: unless-stopped
ports:
- "8080:80"
environment:
PMA_HOST: mysql
MYSQL_ROOT_PASSWORD: rootpassword
depends_on:
- mysql
networks:
- lamp-network

volumes:
mysql-data:

networks:
lamp-network:
driver: bridge

Monitoring Stack (Prometheus + Grafana)


version: '3.8'

services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- monitoring

grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
depends_on:
- prometheus
networks:
- monitoring

node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
networks:
- monitoring

volumes:
prometheus-data:
grafana-data:

networks:
monitoring:
driver: bridge

Best Practices

Dockerfile Best Practices
  • Use specific tags, not latest
  • Minimize layers - Combine RUN commands
  • Use .dockerignore - Exclude unnecessary files
  • Run as non-root user
  • Use multi-stage builds - Keep images small
  • Order layers from least to most frequently changing
  • Use COPY instead of ADD unless you need tar extraction
  • Set timezone if needed
  • Clean up in the same layer to reduce image size
Docker Compose Best Practices
  • Use version control for compose files
  • Use environment files (.env) for sensitive data
  • Define restart policies
  • Use named volumes for data persistence
  • Specify resource limits (memory, CPU)
  • Use health checks
  • Define dependencies properly with depends_on
  • Use networks to isolate services
Security Best Practices
  • Don't run containers as root
  • Scan images for vulnerabilities
  • Don't store secrets in images
  • Use official images from trusted sources
  • Keep images updated
  • Limit container resources
  • Use read-only file systems where possible
  • Enable Docker Content Trust

Troubleshooting

Common Issues
# Container keeps restarting

docker logs <container_name>
docker inspect <container_name>
docker inspect --format='{{json .State.Health}}' <container_name>

# Permission denied

sudo usermod -aG docker $USER
newgrp docker


# Port already in use

sudo lsof -i :<port>
sudo kill -9 <PID>

# Out of disk space

docker system df
docker system prune -a --volumes


# Network issues

docker network ls
docker network inspect <network_name>

# DNS resolution issues

docker run --dns 8.8.8.8 <image>

Useful Resources

Official Docker Documentation Docker Hub Docker Compose Documentation Dockerfile Reference Docker Best Practices