7  Docker and Docker Compose

Docker packages applications into isolated containers — self-contained environments with their own dependencies, libraries, and configuration. Docker Compose orchestrates multiple containers together.

7.1 Why Docker

Without Docker, installing R packages on a server often hits system dependency issues:

Configuration failed to find the Magick++ library. Try installing:
 - deb: libmagick++-dev (Debian, Ubuntu)

A Dockerfile specifies exactly which system libraries, R packages, and configurations your application needs. Build it once, and it runs identically everywhere.

7.2 Containers vs. servers

Think of containers as mini-computers within your server. Each runs its own process in isolation:

  • One container runs your Shiny app
  • Another runs PostgreSQL
  • Another runs Redis
  • NGINX runs on the host and routes traffic to them

Docker Compose ties them all together in a single docker-compose.yaml file.

7.3 The ndexr Docker Compose configuration

version: '3.9'
services:
  ndexrio:
    restart: unless-stopped
    build:
      context: ./services/console
    ports:
      - 9001-9010:8000
    network_mode: bridge
    command: ["R", "-e", "shiny::runApp('/app/src')"]
    profiles:
      - ndexrio

  postgres:
    container_name: ndexr_postgres
    restart: unless-stopped
    env_file: .env
    build:
      context: ./services/postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - '5432:5432'
    network_mode: bridge
    profiles:
      - db

  redis:
    container_name: ndexr_redis
    restart: unless-stopped
    image: redis:6.2-alpine
    command: redis-server
    volumes:
      - redis-data:/data
    ports:
      - '6379:6379'
    network_mode: bridge
    profiles:
      - db

volumes:
  redis-data:
  postgres-data:

Key points:

  • ports: 9001-9010:8000 — maps container port 8000 to host ports 9001 through 9010. When you scale to 10 instances, each gets one of these ports.
  • profiles — lets you selectively start groups of services (ndexrio for the app, db for databases)
  • volumes — persist data across container restarts (database data survives a rebuild)
  • restart: unless-stopped — containers come back after server reboots

7.4 Port mapping and NGINX

The port range in Docker Compose must match the NGINX upstream:

Docker Compose:  ports: 9001-9010:8000
NGINX upstream:  server 127.0.0.1:9001 through 127.0.0.1:9010

When you run make up profile=ndexrio scale=10, Docker starts 10 containers, each listening internally on port 8000 but mapped to ports 9001-9010 on the host. NGINX distributes incoming traffic across all active ports.

7.5 The integration

NGINX, Docker Compose, and Make form the deployment pipeline:

  1. Make runs the commands to build and start containers
  2. Docker Compose manages the containers, networking, and scaling
  3. NGINX routes external traffic to the right containers

Changing your application means rebuilding the container (make up). Scaling means adjusting the worker count (make up scale=20). Changing the server size means modifying the EC2 instance type through the ndexr console.