Docker Containers - Introductory Lesson#

https://hub.docker.com/api/media/repos_logo/v1/library%2Fdocker?type=logo

What are Containers?#

Containers are lightweight, portable, and isolated environments that package applications along with their dependencies. Think of them as:

  • Virtual machines but much more efficient and less resource hungry.

  • Shipping containers for software - they work the same way everywhere

  • Isolated bubbles where your application runs independently

Key Benefits:#

  • Consistency: “It works on my machine” becomes “It works everywhere”

  • Isolation: Applications don’t interfere with each other

  • Portability: Run the same container on development, testing, and production

  • Efficiency: Share the host OS kernel, unlike traditional VMs

Docker Basics#

Docker is the most popular containerization platform. It uses:

  • Images: Read-only templates used to create containers

  • Containers: Running instances of images

  • Dockerfile: Text file with instructions to build an image

  • Docker Hub: Public registry for sharing images

  • Docker Compose: Tool for defining and running multi-container Docker applications

Architecture Overview#

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Application   │    │   Application   │    │   Application   │
├─────────────────┤    ├─────────────────┤    ├─────────────────┤
│   Container     │    │   Container     │    │   Container     │
├─────────────────┤    ├─────────────────┤    ├─────────────────┤
│              Docker Engine                                    │
├───────────────────────────────────────────────────────────────┤
│                    Host Operating System                      │
└───────────────────────────────────────────────────────────────┘
Docker arquitecture

Working with Docker Images#

Pulling Images from Docker Hub#

# Pull the official Ubuntu image
docker pull ubuntu:22.04

# Pull the latest Ubuntu (defaults to latest tag)
docker pull ubuntu

# List downloaded images
docker images

Understanding Image Tags#

  • ubuntu:22.04 - Specific version

  • ubuntu:latest - Latest stable version

  • ubuntu - Defaults to latest

Running Your First Container#

Basic Container Execution#

# Run a simple hello-world container
docker run hello-world

# Run a simple command in Ubuntu container
docker run ubuntu:22.04 echo "Hello, Docker!"

# Run an interactive terminal session
docker run -it ubuntu:22.04 /bin/bash

# Run container in background (detached mode)
docker run -d ubuntu:22.04 sleep 3600

# List running containers
docker ps

# List all containers (including stopped)
docker ps -a
CONTAINER ID   IMAGE         COMMAND       CREATED          STATUS                      PORTS     NAMES
2fa15b7ae800   ubuntu        "/bin/bash"   8 seconds ago    Exited (0) 4 seconds ago              naughty_curran
fbcfe62bfcdb   ubuntu        "/bin/bash"   26 seconds ago   Exited (0) 9 seconds ago              adoring_dhawan
c5fe4f91519a   hello-world   "/hello"      49 seconds ago   Exited (0) 48 seconds ago             vibrant_bhaskara

Command Breakdown:#

  • docker run: Create and start a new container

  • -it: Interactive mode with TTY (terminal)

  • -d: Detached mode (run in background)

  • ubuntu:22.04: Image name and tag

  • /bin/bash: Command to run inside container

Example: Installing Libraries in Containers#

Method 1: Install During Runtime#

# Start an interactive Ubuntu container
docker run -it ubuntu:22.04 /bin/bash

# Inside the container, update package list
apt update

# Install development tools and Eigen library
apt install -y build-essential libeigen3-dev

# Verify installation
pkg-config --modversion eigen3

All this changes will be deleted after exiting the container.

Method 2: Create a Custom Dockerfile#

Create a file named Dockerfile:

# Use Ubuntu 24.04 as base image
FROM ubuntu:24.04

# Avoid interactive prompts during installation
ENV DEBIAN_FRONTEND=noninteractive

# Update package list and install dependencies
RUN apt update && apt install -y \
    build-essential \
    libeigen3-dev \
    cmake \
    pkg-config \
    && rm -rf /var/lib/apt/lists/*

# # Updating gcc/g++
# RUN apt-get update
# RUN apt install software-properties-common -y
# RUN apt install g++-14 -y

# # Installing starship: https://starship.rs/guide/
# RUN apt install curl
# RUN cd /tmp && curl -sS https://starship.rs/install.sh > install_starship.sh  &&  sh install_starship.sh --yes
# RUN echo 'eval "$(starship init bash)"' >> ~/.bashrc

# Exercise: Install git

# Exercise: Install lazygit

# Exercise: Install uv for python management: https://docs.astral.sh/uv/#highlights


# Set working directory
WORKDIR /workspace

# Default command
CMD ["/bin/bash"]

Build the custom image:

# Build image with tag 'cpp-dev'
docker build -t cpp-dev .

# Chek the available images
docker image ls

# Run container from custom image
docker run -it cpp-dev

Sharing Local Directories with Containers#

Volume Mounting#

Volume mounting allows you to share files between your host system and the container.

# Mount current directory to /workspace in container
docker run -it -v $(pwd):/workspace ubuntu:22.04 /bin/bash

# On Windows PowerShell, use:
docker run -it -v ${PWD}:/workspace ubuntu:22.04 /bin/bash

# On Windows Command Prompt, use:
docker run -it -v %cd%:/workspace ubuntu:22.04 /bin/bash

Volume Mount Syntax:#

  • -v host_path:container_path

  • -v $(pwd):/workspace - Mount current directory to /workspace

  • -v /home/user/data:/data - Mount specific host directory

Read-Only Mounts:#

# Mount as read-only
docker run -it -v $(pwd):/workspace:ro ubuntu:22.04

Practical Example: C++ Development with Eigen#

Let’s create a complete example that demonstrates using Docker for C++ development with the Eigen library.

Step 1: Create Project Structure#

mkdir docker-cpp-example
cd docker-cpp-example

Step 2: Create a Simple C++ Program#

Create main.cpp:

#include <iostream>
#include <Eigen/Dense>

int main() {
    // Create a 3x3 matrix
    Eigen::Matrix3d matrix;
    matrix << 1, 2, 3,
              4, 5, 6,
              7, 8, 9;
    
    std::cout << "Matrix:\n" << matrix << std::endl;
    
    // Calculate determinant
    double det = matrix.determinant();
    std::cout << "Determinant: " << det << std::endl;
    
    // Create a vector and multiply
    Eigen::Vector3d vector(1, 2, 3);
    Eigen::Vector3d result = matrix * vector;
    
    std::cout << "Matrix * Vector result:\n" << result << std::endl;
    
    return 0;
}

Step 3: Enhanced Dockerfile#

Create Dockerfile:

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# Install development tools
RUN apt update && apt install -y \
    build-essential \
    cmake \
    libeigen3-dev \
    pkg-config \
    && rm -rf /var/lib/apt/lists/*

# Avoid running as root
# RUN useradd -m -s /bin/bash developer
# USER developer

WORKDIR /workspace

# Copy source files (optional, we'll use volume mounting)
# COPY . /workspace

CMD ["/bin/bash"]

Step 5: Build and Run#

# Build the Docker image
docker build -t cpp-eigen-dev .

# Run container with volume mounting
docker run -it -v $(pwd):/workspace cpp-eigen-dev

# Inside the container, build the project
g++ -I /usr/include/eigen3/ main.cpp -o eigen_example.x

# Run the program
./eigen_example.x

Expected Output:#

Matrix:
1 2 3
4 5 6
7 8 9
Determinant: 0
Matrix * Vector result:
14
32
50

IMPORTANT NOTE: File permissions#

Notice that you are running as root inside the container, so any file produced will belong to root. On other words, you, as a normal user, will not be able to edit those files in the local host. In this case it will be better to run as the local user, like

docker run -it -v $(pwd):/workspace --rm --user "$(id -u):$(id -g)"  cpp-eigen-dev

WARNING: Abusing this root permission will bring consequences.

In this case you can run docker using explicit args for the user, like:

FROM ubuntu:latest

# Replace 1000 with your host user's UID/GID
ARG USER_UID=1000
ARG GROUP_GID=1000

# Create a group and user with matching IDs
RUN groupadd -g ${GROUP_GID} appgroup && \
    useradd -u ${USER_UID} -g appgroup -s /bin/bash -m appuser

# Set ownership for application directory
RUN mkdir /app && chown appuser:appgroup /app

# Switch to the non-root user
USER appuser

WORKDIR /app

# Copy your application code
COPY . .

CMD ["/bin/bash"] # Or your application's command

And then build and run as

docker build --build-arg USER_UID=$(id -u) --build-arg GROUP_GID=$(id -g) -t my-app .
docker run -it --rm -v /host/path:/app my-app

Solution with apptainer/singularity#

In the HPC and scientific computing world it is better to use https://apptainer.org/, like

apptainer shell docker://alpine

You can create a sif apptainer image using

# get image name and tag
docker images

# build the sif image
apptainer build cpp-eigen-dev.sif docker-daemon:cpp-eigen-dev:latest

and then run a shell as

apptainer shell cpp-eigen-dev.sif

You can also use your Dockerfile to create an apptainer def file, using spython

# install the utility
pip3 install spython

# Create the def file
spython recipe Dockerfile > cpp-eigen-dev.def

# Build your image
apptainer build cpp-eigen-dev.sif cpp-eigen-dev.def

# Easier way but requires more recent apptainer
# apptainer build --oci your_image.sif dockerfile://./Dockerfile

Common Docker Commands#

Container Management#

# Start a stopped container
docker start <container_id>

# Stop a running container
docker stop <container_id>

# Remove a container
docker rm <container_id>

# Remove all stopped containers
docker container prune

Image Management#

# List images
docker images

# Remove an image
docker rmi <image_id>

# Remove unused images
docker image prune

# View image history
docker history <image_name>

Debugging and Inspection#

# View container logs
docker logs <container_id>

# Execute command in running container
docker exec -it <container_id> /bin/bash

# Inspect container details
docker inspect <container_id>

# View container resource usage
docker stats

Best Practices#

1. Use Specific Image Tags#

# Good
FROM ubuntu:22.04

# Avoid
FROM ubuntu:latest

2. Minimize Image Layers#

# Good - Single RUN command
RUN apt update && apt install -y \
    package1 \
    package2 \
    && rm -rf /var/lib/apt/lists/*

# Avoid - Multiple RUN commands
RUN apt update
RUN apt install -y package1
RUN apt install -y package2

3. Use .dockerignore#

Create .dockerignore to exclude unnecessary files:

.git
.gitignore
README.md
Dockerfile
.dockerignore
node_modules
*.log

4. Don’t Run as Root#

# Create non-root user
RUN useradd -m -s /bin/bash developer
USER developer

5. Clean Up Package Caches#

RUN apt update && apt install -y \
    package1 \
    package2 \
    && rm -rf /var/lib/apt/lists/*

Exercises#

Exercise 1: Basic Container Operations#

  1. Pull the Python 3.9 image from Docker Hub

  2. Run a Python container and execute: python -c "print('Hello from Docker!')"

  3. Start an interactive Python session in a container

Exercise 2: Volume Mounting Practice#

  1. Create a local directory with a text file

  2. Mount it to a container and read the file from inside the container

  3. Create a new file inside the container and verify it appears on your host

Exercise 3: Custom Development Environment#

  1. Create a Dockerfile for a Node.js development environment

  2. Include Node.js, npm, and git

  3. Set up a working directory

  4. Build and test your custom image

Exercise 4: Multi-Step Build#

  1. Extend the C++ Eigen example

  2. Add additional mathematical operations

  3. Build and run everything in Docker

Exercise 5: Container Networking#

  1. Run a web server (like nginx) in a container

  2. Map port 80 in the container to port 8080 on your host

  3. Access the web server from your browser

Solutions Hints:#

Exercise 1:

docker pull python:3.9
docker run python:3.9 python -c "print('Hello from Docker!')"
docker run -it python:3.9 python

Exercise 2:

mkdir test-data
echo "Hello from host" > test-data/hello.txt
docker run -it -v $(pwd)/test-data:/data ubuntu:22.04 /bin/bash
# Inside container: cat /data/hello.txt
# Inside container: echo "Hello from container" > /data/container.txt

Exercise 5:

docker run -d -p 8080:80 nginx:alpine
# Open browser to http://localhost:8080

Conclusion#

Docker containers provide a powerful way to:

  • Standardize development environments

  • Ensure consistent deployments

  • Isolate applications and dependencies

  • Share and distribute software easily

Key concepts covered:

  • Images vs. Containers

  • Volume mounting for file sharing

  • Installing libraries and dependencies

  • Building custom images with Dockerfiles

  • Practical C++ development example

Continue practicing with different base images, programming languages, and use cases to master containerization!

ROOTLESS containers