0. Prerequisites: Install on Ubuntu

Setup

Before writing Compose files, you need the engine running. Here are the commands to install Docker on a fresh Ubuntu 22.04/24.04 server.

Installation Script
# 1. Update and install dependencies
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

# 2. Add Docker GPG Key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 3. Add Docker Repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 4. Install Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io

# 5. Add user to docker group (No more 'sudo' required)
sudo usermod -aG docker ${USER}
# Note: You must logout and login again for step 5 to take effect!

1. Basic Structure

Phase 1: Starting Point

This is the bare minimum required to get services running. It creates containers but lacks persistence and networking configuration.

Docker Compose Basic vs Enterprise Architecture Diagram
Figure 1: Visual comparison of Basic vs. Enterprise Docker setups.
The Code
version: '3.8'
services:
  frontendjenkins:
    image: jenkins/jenkins
    ports:
      - "8080:8080"

  qaserver:
    image: tomee
    ports:
      - "7070:8080"
    depends_on:
      - frontendjenkins
Architectural Analysis

In this basic setup, Docker creates ephemeral containers. This means that while the application runs perfectly for a quick demo, it is catastrophic for production. When you stop the frontendjenkins container, the underlying file system is destroyed. All your build history, user accounts, and plugin configurations vanish instantly. Furthermore, because we haven't defined a custom network, these containers run on the default "Bridge" network, where DNS resolution is limited.

2. Intermediate Structure

Phase 2: Developer Ready

Here we introduce stability. We add Volumes for data persistence and Custom Networks for secure communication.

services:
  jenkins:
    container_name: jenkins
    volumes:
      - jenkins_home:/var/jenkins_home
    networks:
      - devnet

volumes:
  jenkins_home:

networks:
  devnet:
    driver: bridge
Understanding Service Discovery

By introducing a custom user-defined bridge network (`devnet`), we unlock Docker's internal DNS resolution. Now, the Jenkins container can ping the QA server simply by using the hostname `qaserver`. We don't need to hardcode IP addresses, which change every time a container restarts. Additionally, the Named Volume (`jenkins_home`) is now decoupled from the container lifecycle. Even if you delete the container entirely, your data persists in `/var/lib/docker/volumes/`, ready to be reattached.

3. Enterprise Integrated Stack

Phase 3: Company Grade

The final evolution. We integrate PostgreSQL, add Healthchecks, and attach a full Monitoring Stack.

version: '3.8'
services:
  # --- CI/CD & App Services ---
  jenkins:
    image: jenkins/jenkins:lts
    ports: ["8080:8080", "50000:50000"]
    volumes: [ "jenkins_home:/var/jenkins_home" ]
    networks: [ "devnet" ]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/login"]
      interval: 30s
      retries: 5

  qaserver:
    image: tomee:latest
    depends_on:
      jenkins:
        condition: service_healthy
    networks: [ "devnet" ]
    environment:
      - APP_ENV=qa
      - DB_HOST=postgres_db

  # --- Database ---
  db:
    image: postgres:15
    container_name: postgres_db
    environment:
      - POSTGRES_DB=companydb
    volumes: [ "db_data:/var/lib/postgresql/data" ]
    networks: [ "devnet" ]

  # --- Monitoring ---
  prometheus:
    image: prom/prometheus:latest
    ports: [ "9090:9090" ]
    networks: [ "devnet" ]

  grafana:
    image: grafana/grafana:latest
    ports: [ "3000:3000" ]
    depends_on: [ "prometheus" ]
    networks: [ "devnet" ]

volumes:
  jenkins_home:
  db_data:

networks:
  devnet:
    driver: bridge
Solving the "Race Condition"

One of the most common failures in CI/CD stacks is the "Race Condition." Usually, a database takes 10–15 seconds to initialize, but the application tries to connect in 2 seconds. The app crashes, restarts, and crashes again. In this setup, we use depends_on: condition: service_healthy. The QA Server will strictly wait until Jenkins reports a "Healthy" status via its internal curl check before it even attempts to start.

4. Essential Docker Commands

Cheat Sheet

A quick reference for the single-line commands you will use daily.

Command Description
docker ps -a List running and stopped containers.
docker images List all downloaded images.
docker logs -f [id] View live logs of a container.
docker exec -it [id] bash Go inside a running container (shell).
docker system prune -a DANGER: Deletes all stopped containers, unused networks, and images.
docker-compose up -d --build Build and start containers in the background.