Container Concepts

Week 11: Docker and Containerization - Monday Lecture

Introduction to Containers

Containers have revolutionized how we develop, deploy, and run applications. But what exactly are containers? At their core, containers are a standardized unit of software that packages code and all its dependencies so the application runs quickly and reliably across different computing environments.

Imagine you're moving to a new house. You could transport all your belongings individually, carefully placing each item in the moving truck, and then unloading them one by one at your new home. This would be time-consuming and risky - items could break or get lost in transit. Alternatively, you could use standardized shipping containers that protect your belongings, stack efficiently, and transfer seamlessly between trucks, trains, and ships.

Software containers work in a similar way. Instead of deploying applications directly onto different environments (development, testing, production) with different configurations, we package everything the application needs in a standardized "container" that works the same way regardless of where it runs.

graph TD subgraph "Traditional Deployment" A[Application] --> B[Libraries] B --> C[OS] C --> D[Hardware] end subgraph "Container Deployment" E[Container with App + Libraries] --> F[Container Runtime] F --> G[OS] G --> H[Hardware] end style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#eeeeee,stroke:#333,stroke-width:2px style C fill:#dddddd,stroke:#333,stroke-width:2px style D fill:#cccccc,stroke:#333,stroke-width:2px style E fill:#f9d5e5,stroke:#333,stroke-width:2px style F fill:#d5f5e3,stroke:#333,stroke-width:2px style G fill:#dddddd,stroke:#333,stroke-width:2px style H fill:#cccccc,stroke:#333,stroke-width:2px

Containers vs. Virtual Machines

To understand containers better, it's helpful to compare them with virtual machines (VMs), another technology for isolating applications.

graph TD subgraph "Virtual Machine Architecture" A1[App A] --> B1[Libraries] B1 --> C1[Guest OS 1] A2[App B] --> B2[Libraries] B2 --> C2[Guest OS 2] C1 --> D[Hypervisor] C2 --> D D --> E[Host OS] E --> F[Infrastructure] end subgraph "Container Architecture" G1[App A + Libs] --> I[Container Runtime] G2[App B + Libs] --> I I --> J[Host OS] J --> K[Infrastructure] end style A1 fill:#f9d5e5,stroke:#333,stroke-width:2px style A2 fill:#f9d5e5,stroke:#333,stroke-width:2px style G1 fill:#f9d5e5,stroke:#333,stroke-width:2px style G2 fill:#f9d5e5,stroke:#333,stroke-width:2px

Key Differences

Virtual Machines Containers
Include full operating system Share host operating system
Heavyweight (GBs in size) Lightweight (MBs in size)
Slow to start (minutes) Quick to start (seconds)
Complete isolation Process-level isolation
Hardware-level virtualization OS-level virtualization

Think of a virtual machine as renting an entire apartment while a container is like booking a room in a co-living space. The VM gives you an entire operating system with all its overhead, while containers share the core operating system but keep your "stuff" (application and dependencies) separate from other containers.

Container Technology Fundamentals

How Containers Work

Containers leverage several Linux kernel features to create isolated environments for applications:

graph TB subgraph "Container Isolation Technology" A[Namespaces] --> D[Container Isolation] B[Control Groups] --> D C[Union File Systems] --> D end style A fill:#d5f5e3,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#d5f5e3,stroke:#333,stroke-width:2px style D fill:#f9d5e5,stroke:#333,stroke-width:2px

Think of namespaces like having a one-way mirror around your apartment. You can see out, but neighbors can't see in, and you can't directly interact with their stuff. Control groups are like having an electricity meter and water usage limits for each apartment. Union file systems are similar to how modern photo editing software uses layers - each edit is a new transparent layer placed on top of the original image.

Container Images and Containers

There's an important distinction between container images and running containers:

flowchart LR A[Container Image] --> B[Container 1] A --> C[Container 2] A --> D[Container 3] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#d5f5e3,stroke:#333,stroke-width:2px style D fill:#d5f5e3,stroke:#333,stroke-width:2px

This relationship is similar to how a cake recipe (the image) can be used to bake multiple actual cakes (the containers). Each cake follows the same recipe but exists as a separate entity that can be consumed independently.

Image Layers

Container images are composed of layers, which are cached and reused for efficiency:

graph TD A[Base OS Layer] --> B[Runtime Layer] B --> C[Dependencies Layer] C --> D[Application Code Layer] style A fill:#d5f5e3,stroke:#333,stroke-width:2px style B fill:#f9d5e5,stroke:#333,stroke-width:2px style C fill:#ebdef0,stroke:#333,stroke-width:2px style D fill:#eaeded,stroke:#333,stroke-width:2px

Each layer only stores the changes from the previous layer. This makes sharing of common layers efficient - if you have multiple Node.js applications, they can all share the same Node.js runtime layer without duplication.

This layering is like building a burger. The base layer is the bottom bun (OS), followed by lettuce (runtime), cheese (dependencies), and finally the patty (your application code). When you create multiple burgers, you don't need to reinvent the bun - you just reuse the same type of ingredients for each one.

Container Runtimes and Engines

Let's clarify some important terminology in the container ecosystem:

graph TD A[User Commands] --> B[Container Engine] B --> C[Container Runtime] C --> D[Operating System] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#ebdef0,stroke:#333,stroke-width:2px style D fill:#eaeded,stroke:#333,stroke-width:2px

If we use a car analogy, the container runtime is like the engine and drivetrain that actually moves the car, while the container engine is like the dashboard, steering wheel, and pedals that make it user-friendly to operate.

The Open Container Initiative (OCI)

The OCI is an open governance structure for creating open industry standards around container formats and runtimes. It ensures compatibility between different container tools and platforms.

This standardization is similar to how shipping containers have standard dimensions, allowing them to be moved seamlessly between ships, trains, and trucks regardless of the manufacturer.

Benefits of Using Containers

Consistency Across Environments

Containers solve the "it works on my machine" problem by ensuring consistent environments from development to production.

Resource Efficiency

Containers share the host OS kernel and use fewer resources than VMs, allowing for higher density of applications per server.

Rapid Deployment and Scaling

Containers start in seconds and can be easily scaled horizontally to handle increased load.

Isolation and Security

Each container runs in isolation, reducing the impact of vulnerabilities and conflicts between applications.

Microservices Architecture

Containers are ideal for microservices, allowing different parts of an application to be developed, deployed, and scaled independently.

graph TD A[Monolithic App] --> B[All Components in One Codebase] C[Microservices] --> D[Service 1
Container] C --> E[Service 2
Container] C --> F[Service 3
Container] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#f9d5e5,stroke:#333,stroke-width:2px style C fill:#d5f5e3,stroke:#333,stroke-width:2px style D fill:#d5f5e3,stroke:#333,stroke-width:2px style E fill:#d5f5e3,stroke:#333,stroke-width:2px style F fill:#d5f5e3,stroke:#333,stroke-width:2px

Using a restaurant analogy, traditional deployment is like having one chef handle everything from appetizers to desserts. Microservices with containers are like having specialized chefs for each type of dish, working independently but coordinating to serve a complete meal. If the dessert station gets busy, you can add more dessert chefs without affecting the appetizer station.

Real-World Container Use Cases

Development Environments

Developers can use containers to create consistent development environments that match production. No more "it works on my machine" problems when collaborating with other developers.

Continuous Integration/Continuous Deployment (CI/CD)

Containers allow CI/CD pipelines to build, test, and deploy applications in consistent environments, making the process more reliable.

graph LR A[Code Repository] --> B[Build Container] B --> C[Test Container] C --> D[Deploy Container] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#ebdef0,stroke:#333,stroke-width:2px style D fill:#eaeded,stroke:#333,stroke-width:2px

Microservices Architecture

Companies like Netflix, Uber, and Airbnb use containers to deploy and scale hundreds or thousands of microservices independently.

Legacy Application Modernization

Containers can help modernize legacy applications by containerizing them first, then gradually breaking them into microservices.

Multi-Cloud Strategy

Containers make it easier to deploy applications across multiple cloud providers, avoiding vendor lock-in.

Edge Computing

Containers are ideal for edge computing scenarios where resources are limited and efficiency is critical.

Common Container Tools and Platforms

Docker

Docker is the most popular container platform, providing tools for building, running, and managing containers. It includes:

Kubernetes

Kubernetes is an open-source container orchestration platform that automates deployment, scaling, and management of containerized applications.

Container Registries

Services like Docker Hub, Google Container Registry, Amazon ECR, and GitHub Container Registry store and distribute container images.

Container-as-a-Service (CaaS)

Managed container platforms like Amazon ECS, Google Cloud Run, and Azure Container Instances simplify container deployment and management.

graph TB A[Container Tools Ecosystem] --> B[Container Engines
Docker, Podman, containerd] A --> C[Orchestration
Kubernetes, Docker Swarm] A --> D[Registries
Docker Hub, ECR, GCR] A --> E[CaaS
ECS, Cloud Run, ACI] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#ebdef0,stroke:#333,stroke-width:2px style D fill:#eaeded,stroke:#333,stroke-width:2px style E fill:#fdebd0,stroke:#333,stroke-width:2px

Container Challenges and Best Practices

Security Considerations

While containers provide isolation, they share the host kernel, which can introduce security concerns:

Stateful vs. Stateless Containers

Containers are ephemeral by nature, making stateful applications challenging:

Monitoring and Logging

The dynamic nature of containers requires specialized monitoring approaches:

Resource Management

Properly configure resource limits to ensure containers don't impact each other:

Getting Started with Containers

Basic Container Workflow

graph LR A[Write Dockerfile] --> B[Build Image] B --> C[Run Container] C --> D[Push to Registry] D --> E[Deploy to Production] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#ebdef0,stroke:#333,stroke-width:2px style D fill:#eaeded,stroke:#333,stroke-width:2px style E fill:#fdebd0,stroke:#333,stroke-width:2px

Simple Dockerfile Example

A Dockerfile is a text file that contains instructions for building a Docker image:


# Use an official Node.js runtime as the base image
FROM node:14-alpine

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code
COPY . .

# Expose the port the app runs on
EXPOSE 3000

# Command to run the application
CMD ["npm", "start"]
            

Each line in the Dockerfile creates a new image layer:

Basic Docker Commands

Here are some essential Docker commands to get started:


# Build an image from a Dockerfile
docker build -t myapp:1.0 .

# Run a container from an image
docker run -p 3000:3000 myapp:1.0

# List running containers
docker ps

# Stop a container
docker stop [container_id]

# View logs from a container
docker logs [container_id]

# Execute a command in a running container
docker exec -it [container_id] /bin/sh

# Pull an image from a registry
docker pull nginx:latest

# Push an image to a registry
docker push username/myapp:1.0
            

Containerizing a JavaScript Application

Example: Containerizing a Node.js Web Application

Project Structure


my-node-app/
├── src/
│   ├── index.js
│   └── ... (other source files)
├── package.json
├── package-lock.json
└── Dockerfile
            

Sample Application (src/index.js)


const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello from a containerized Node.js app!');
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});
            

Package.json


{
  "name": "my-node-app",
  "version": "1.0.0",
  "description": "A simple Node.js app in a container",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js"
  },
  "dependencies": {
    "express": "^4.17.1"
  }
}
            

Dockerfile


# Use an official Node.js runtime as the base image
FROM node:14-alpine

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code
COPY ./src ./src

# Expose the port the app runs on
EXPOSE 3000

# Command to run the application
CMD ["npm", "start"]
            

Building and Running the Container


# Build the Docker image
docker build -t my-node-app .

# Run the container
docker run -p 3000:3000 my-node-app
            

Now you can access your application at http://localhost:3000 in your browser.

Multi-Container Applications with Docker Compose

Most real-world applications consist of multiple services (web server, database, cache, etc.). Docker Compose allows you to define and run multi-container applications.

Example: Node.js App with MongoDB

Docker Compose File (docker-compose.yml)


version: '3'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - MONGODB_URI=mongodb://db:27017/myapp
    depends_on:
      - db
  
  db:
    image: mongo:4.4
    volumes:
      - mongo-data:/data/db
    ports:
      - "27017:27017"

volumes:
  mongo-data:
            

This Docker Compose file defines two services:

It also defines a volume to persist the MongoDB data even if the container is removed.

Running with Docker Compose


# Start the services
docker-compose up

# Start in detached mode (background)
docker-compose up -d

# Stop services
docker-compose down

# Stop services and remove volumes
docker-compose down -v
            

Container Orchestration Preview

As you scale from a few containers to dozens or hundreds, managing them becomes challenging. Container orchestration platforms automate deployment, scaling, and management of containerized applications.

Kubernetes

Kubernetes is the most popular container orchestration platform, providing features like:

graph TD A[Kubernetes Cluster] --> B[Control Plane] A --> C[Worker Nodes] B --> D[API Server] B --> E[Scheduler] B --> F[Controller Manager] B --> G[etcd] C --> H[Node 1] C --> I[Node 2] C --> J[Node N] H --> K[Pod 1] H --> L[Pod 2] style A fill:#f9d5e5,stroke:#333,stroke-width:2px style B fill:#d5f5e3,stroke:#333,stroke-width:2px style C fill:#d5f5e3,stroke:#333,stroke-width:2px

Think of Kubernetes as a team of managers and workers in a large restaurant. The control plane (managers) decides which dishes (containers) to prepare, and assigns them to specific kitchen stations (worker nodes). If a chef (container) gets sick, or a kitchen station becomes too busy, the managers automatically reassign work to maintain efficiency.

Docker Swarm

Docker Swarm is Docker's native clustering and orchestration solution. It's simpler than Kubernetes but has fewer features. We'll explore both in more detail later this week.

Conclusion

Containers have transformed how we develop, deploy, and run applications by providing consistency, efficiency, and isolation. They are the foundation of modern cloud-native architectures and enable practices like microservices, CI/CD, and DevOps.

In the coming lectures, we'll dive deeper into Docker, container orchestration, and best practices for containerizing applications.

mindmap root((Containers)) Technology Namespaces Control Groups Union File Systems Concepts Images Layers Registries Benefits Consistency Efficiency Scalability Isolation Tools Docker Kubernetes Docker Compose Use Cases Development CI/CD Microservices Legacy Modernization

Practice Activities

Activity 1: Install Docker and Run Your First Container

  1. Install Docker Desktop on your machine (Windows/Mac) or Docker Engine (Linux).
  2. Verify installation with docker version and docker info.
  3. Run your first container: docker run hello-world.
  4. Pull and run an Nginx web server: docker run -d -p 8080:80 nginx.
  5. Visit http://localhost:8080 to see the default Nginx page.
  6. Explore running containers with docker ps and stop them with docker stop [container_id].

Activity 2: Containerize a Simple Web Application

  1. Create a simple HTML page or use an existing web application.
  2. Write a Dockerfile to serve it using Nginx or a simple HTTP server.
  3. Build and run your container.
  4. Make changes to your application and rebuild to see the containerization workflow.

Activity 3: Explore Container Isolation

  1. Run two instances of the same container with different ports.
  2. Execute commands inside running containers with docker exec -it [container_id] /bin/sh.
  3. Create files inside one container and verify they're not visible in another container.
  4. Experiment with environment variables to customize container behavior.

Activity 4: Create a Multi-Container Application

  1. Create a simple web application that connects to a database.
  2. Write a Docker Compose file to define both services.
  3. Use Docker networks to allow the containers to communicate.
  4. Use Docker volumes to persist data outside the container lifecycle.

Challenge: Containerize Your Final Project

Take your existing final project (or another complex application) and containerize it:

  1. Write Dockerfiles for each component (frontend, backend, database, etc.).
  2. Create a Docker Compose configuration to run the entire stack.
  3. Document the containerization process and challenges you encountered.
  4. Prepare to present your containerized application to the class.

Additional Resources

Documentation

Tutorials and Courses

Books

Community and Forums