Installation on RHEL-based Systems
Enable the Docker repository and install required components:
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Start and enable the service:
sudo systemctl enable --now docker
sudo docker run --rm hello-world
Database Container Deployment
MySQL Instance for Development
Pull and launch a data base container with custom configuration:
docker pull mysql:8.0
docker run -d \
--name database-server \
-p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=DevPass123! \
mysql:8.0
Connect from remote host:
mysql -u root -p -h 192.168.1.100 -P 3307
Persistent Storage Configuration
Create host directory and mount as volume:
sudo mkdir -p /data/containers/mysql-db
docker run -d \
--name persistent-mysql \
-p 3308:3306 \
-e MYSQL_ROOT_PASSWORD=SecurePass456! \
-v /data/containers/mysql-db:/var/lib/mysql \
mysql:8.0
Redis with Host Networking
When using host network mode, port mapping is unnecessary:
docker run -d \
--name cache-server \
--restart unless-stopped \
--network host \
redis:7-alpine
Host mode allows localhost access from the container host, while bridged mode requires external IP addresses.
Container Inspection Commands
Execute commands inside running containers:
docker exec -it database-server mysql -uroot -p
View runtime information:
docker logs database-server 2>&1 | tail -50
docker port database-server
docker inspect database-server --format='{{json .NetworkSettings.Networks}}'
docker inspect --format='{{range .Mounts}}{{print .Source ":" .Destination}}{{end}}' database-server
Dockerfile Creation and Build Process
Build a custom image from a Dockerfile:
docker build -t custom-app:v1.2 .
Key options:
-fspecifies Dockerfile path (default: PATH/Dockerfile)-tsets image name and tag
Python Application Example
Directory structure:
project/
├── Dockerfile
├── requirements.txt
└── app.py
Dockerfile content:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
USER nobody
CMD ["python", "app.py"]
Build command:
docker build -t python-api:1.0.0 -f Dockerfile .
Base Image Selection Strategy
Base images provide the foundation for custom images. The first FROM instruction defines this foundation.
Minimal Base Images
scratch: A virtual empty image. Ideal for statically compiled binaries:
FROM scratch
COPY mybinary /mybinary
CMD ["/mybinary"]
alpine: Lightweight security-oriented distribution (5MB):
FROM alpine:3.18
RUN apk add --no-cache ca-certificates
Operating System Images
| Image | Size | Primary Use Case |
|---|---|---|
| busybox | 1.2MB | Temporary testing |
| alpine | 5.5MB | Testing and production |
| ubuntu | 80MB | Production, AI/ML workloads |
| debian | 120MB | Production stability |
| centos | 200MB | Enterprise legacy systems |
Language-Specific Images
Java: OpenJDK images avoid licensing issues
FROM openjdk:17-jre-slim
Node.js:
FROM node:18-alpine
Python:
FROM python:3.11-slim
Multi-Stage Build Optimization
Separate build and runtime environments to reduce final image size:
# Build stage
FROM golang:1.20-alpine AS compile-stage
RUN apk add --no-cache git
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/server ./cmd/server
# Runtime stage
FROM alpine:3.18
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=compile-stage /bin/server /app/server
RUN addgroup -g 10001 -S appgroup && \
adduser -u 10001 -S appuser -G appgroup
USER appuser
EXPOSE 8080
CMD ["/app/server"]
This pattern keeps compliation tools out of the production image.
COPY Instruction Constraints
The <src> path must be within the build context (the . directory). These are invalid:
COPY ../file /dest # Invalid: outside context
COPY /etc/hosts /dest # Invalid: absolute path
Copying between stages is supported:
COPY --from=compile-stage /bin/app /app
Container Process Management
PID 1 has unique responsibilities: reaping zombie processes and signal handling.
Zombie Process Reaping
Zombie processes are terminated processes whose exit status hasn't been read by their parent. When parent processes exit, zombies become children of PID 1, which must clean them up.
Applications like Jenkins create zombies unavoidably when running user-provided scripts. Init systems like tini handle this cleanup.
Signal Forwarding
Pure PID 1 processes lack default signal handlers. Sending SIGTERM to such a process gets discarded unless explicitly handled.
Consider this process tree:
PID USER COMMAND
1 app /bin/tini -- /app/server
6 app /app/server
Tini receives signals and forwards them to child processes. Without tini:
# Using bash as PID 1
docker run myapp bash -c "/app/server"
Signals sent to the container reach bash, which doesn't forward them by default.
Exit Code Propagation
Tini ensures the container exits with the child's status code. Using bash might mask crashes with exit code 0.
Example Dockerfile using tini:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y tini
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/app/server"]
Many official images already include tini or similar init systems for proper process management.