Docker Containers - Introductory Lesson#
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 │
└───────────────────────────────────────────────────────────────┘

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
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
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#
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#
Pull the Python 3.9 image from Docker Hub
Run a Python container and execute:
python -c "print('Hello from Docker!')"
Start an interactive Python session in a container
Exercise 2: Volume Mounting Practice#
Create a local directory with a text file
Mount it to a container and read the file from inside the container
Create a new file inside the container and verify it appears on your host
Exercise 3: Custom Development Environment#
Create a Dockerfile for a Node.js development environment
Include Node.js, npm, and git
Set up a working directory
Build and test your custom image
Exercise 4: Multi-Step Build#
Extend the C++ Eigen example
Add additional mathematical operations
Build and run everything in Docker
Exercise 5: Container Networking#
Run a web server (like nginx) in a container
Map port 80 in the container to port 8080 on your host
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