Docker Deployment and Image Construction: Practical Patterns and Process Management

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:

  • -f specifies Dockerfile path (default: PATH/Dockerfile)
  • -t sets 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.

Tags: docker containerization dockerfile devops multi-stage-build

Posted on Sun, 11 Oct 2026 16:07:49 +0000 by sebnewyork