Virtual machines have been a cornerstone of IT for years, but there’s a more efficient technology that’s now an essential part of modern infrastructure: Linux Containers.
Containers offer a lightweight, efficient, and incredibly flexible way to package, deploy, and manage applications. If you’re an IT administrator working with Ubuntu, understanding containers is becoming increasingly essential.
This guide will give you a solid foundation in Linux containers. We’ll cover the core concepts, explore the advantages of containers, and walk through practical examples of using them on Ubuntu. While the examples were initially tested on Ubuntu 20.04, the concepts and commands are applicable to Ubuntu 22.04 and 24.04 as well, with only minor variations that we’ll note where relevant.
Imagine you could package up an application and all its dependencies (libraries, configuration files, runtime environment) into a single, self-contained unit. That’s essentially what a container does. Unlike virtual machines (VMs), which include an entire operating system, containers share the kernel of the host operating system. This makes them much smaller and faster than VMs.
Containers vs. Virtual Machines: Understanding the Difference
Containers are incredibly versatile. Here are some common use cases:
Package an application and its dependencies into a single, portable unit. This eliminates the “it works on my machine” problem and streamlines deployment.
Break down large applications into smaller, independent services, each running in its own container. This approach improves scalability, development velocity, and application resilience.
Create consistent, reproducible environments for development and testing. Every developer can run the exact same environment locally, matching production.
Integrate containers into your CI/CD pipeline to automate building, testing, and deploying applications. Containers ensure consistency across the entire pipeline.
Containers are a fundamental building block of cloud-native applications, enabling efficient resource utilization and simplified infrastructure management.
Run many isolated applications on a single server, maximizing resource utilization and reducing infrastructure costs.
Container Use Cases: Where They Shine
While Docker is the most well-known container platform, Ubuntu and the wider Linux community offer a powerful set of alternative tools, often preferred for their security and flexibility. These tools are fully compatible with Docker images and follow the Open Container Initiative (OCI) standards.
Read: Docker container orchestration tools
Container images are stored in registries. A registry is like a central repository for images. The most well-known registry is the Docker Hub, but there are others, including:
You can pull images from registries to your local system, and you can push your own images to registries to share them with others or deploy them to different environments.
When using public registries, always verify the source of the images you’re using. Prefer official images when available, and check for security vulnerabilities using tools like Trivy or Clair.
By default, containers on Ubuntu use a bridged network via the Container Networking Interface (CNI).
To make services inside a container accessible from outside, you’ll need to expose ports. Here’s how to do it with podman:
# Run a web server container and expose port 8080 on the host
podman run -d --name webserver -p 8080:80 docker.io/library/nginx
This command maps port 8080 on the host to port 80 in the container. You can then access the web server by navigating to http://localhost:8080 on the host machine.
Read: How to Pass Environment Variables to Docker Containers
For more complex networking scenarios, you can create custom networks:
# Create a new network
podman network create mynetwork
# Run a container on the custom network
podman run -d --name db --network mynetwork postgres
# Run another container on the same network
podman run -d --name api --network mynetwork my-api-image
Containers on the same custom network can communicate with each other using their container names as hostnames.
Let’s install the essential tools (podman, buildah, skopeo) on your Ubuntu system:
sudo apt install curl -y
The Kubic Project (maintained by the openSUSE community) provides up-to-date packages for these container tools. These commands add the repository and its signing key:
sudo sh -c "echo 'deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$(. /etc/os-release; echo $VERSION_ID)/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
curl -L "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$(. /etc/os-release; echo $VERSION_ID)/Release.key" | sudo apt-key add -
What’s happening?
Note for Ubuntu 22.04 and newer: The
apt-keycommand is deprecated. For newer Ubuntu versions, use the following approach instead:curl -L "https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$(. /etc/os-release; echo $VERSION_ID)/Release.key" | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/kubic.gpg
sudo apt update
sudo apt install podman buildah skopeo -y
Rootless containers allow you to run containers without root privileges, which significantly improves security by reducing the potential impact of container breakouts.
# Install the required packages
sudo apt install uidmap slirp4netns fuse-overlayfs -y
# Configure subuid and subgid mappings if not already present
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
# Set up correct path for XDG_RUNTIME_DIR
echo 'export XDG_RUNTIME_DIR=/run/user/$UID' >> ~/.bashrc
source ~/.bashrc
# Verify podman works in rootless mode
podman run --rm docker.io/library/alpine echo "Hello from rootless container!"
If everything is set up correctly, you’ll see the output without needing to use sudo.
Read: Secure Your Ubuntu 24.04 System: 30 Essential Steps for Enhanced Security
Before you can run a container, you need an image. An image is a read-only template that contains everything needed to run an application (code, libraries, runtime, environment variables, etc.). Let’s pull the official Ubuntu image from the Docker Hub:
podman pull docker://docker.io/library/ubuntu:latest
Explanation:
podman pull: This command downloads an image from a registry.docker://: This specifies that we’re using the Docker image format.docker.io/library/ubuntu: This is the image name. docker.io: The registry (Docker Hub).library/ubuntu: The repository (official Ubuntu images).ubuntu: The image name.:latest: This is the tag. Tags are used to identify different versions of an image. latest usually refers to the most recent stable release.You can use skopeo to get information about an image without downloading it:
skopeo inspect docker://docker.io/library/ubuntu:latest
This will show you metadata about the image, including its layers, architecture, and environment variables.
You can also use podman to inspect a downloaded image:
podman inspect ubuntu:latest
For production environments, it’s important to scan container images for security vulnerabilities:
# Install trivy (vulnerability scanner)
sudo apt install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt update
sudo apt install -y trivy
# Scan an image
trivy image docker.io/library/ubuntu:latest
Read: How to clean up unused Docker containers, images and volumes
Now that you have an image, you can run it in a container.
podman run --rm ubuntu:latest cat /etc/passwd
Explanation:
podman run: This command creates and starts a container.--rm: This option tells podman to remove the container after it exits. This is useful for short-lived tasks.ubuntu:latest: The image to use.cat /etc/passwd: The command to run inside the container. This will display the contents of the /etc/passwd file from the container’s filesystem (which is different from your host system’s /etc/passwd file).podman run -it --name=mycontainer ubuntu:latest /bin/bash
Explanation:
-it: This combines two options: -i (interactive): Keeps STDIN open, even if not attached.-t (tty): Allocates a pseudo-TTY (a terminal). This is what gives you an interactive shell.--name=mycontainer: Assigns a name to the container. This makes it easier to refer to the container later (instead of using a randomly generated ID)./bin/bash: The command to run inside the container. This starts a Bash shell.Once you run this command, you’ll be inside the container’s shell. You can run commands, explore the filesystem, and even install software.
podman run -d --name=webserver -p 8080:80 docker.io/library/nginx
Explanation:
-d: Runs the container in detached (background) mode.-p 8080:80: Maps port 8080 on the host to port 80 in the container.docker.io/library/nginx: The official NGINX web server image.You can now access the web server by navigating to http://localhost:8080 in your web browser.
# List running containers
podman ps
# List all containers (including stopped ones)
podman ps -a
# Stop a running container
podman stop mycontainer
# Start a stopped container
podman start mycontainer
# Restart a container
podman restart mycontainer
# Pause and unpause a container
podman pause mycontainer
podman unpause mycontainer
# Remove a container (must be stopped first)
podman rm mycontainer
# Force remove a running container
podman rm -f mycontainer
# Execute a command in a running container
podman exec -it mycontainer bash
# Attach to a running container's primary process
podman attach mycontainer
# View container logs
podman logs mycontainer
# Follow container logs in real-time
podman logs -f mycontainer
# View container stats (CPU, memory, network, etc.)
podman stats
# View stats for a specific container
podman stats mycontainer
Containers are ephemeral by design – when a container is removed, any data stored inside it is lost. Volumes provide a way to persist data outside the container lifecycle.
# Create a named volume
podman volume create mydata
# List volumes
podman volume ls
# Inspect a volume
podman volume inspect mydata
# Remove a volume
podman volume rm mydata # Run a container with a volume
podman run -d --name=db -v mydata:/var/lib/postgresql/data postgres
# Use a bind mount to share a host directory
podman run -v /path/on/host:/path/in/container ubuntu:latest ls /path/in/container
# Backup a volume
podman run --rm -v mydata:/source -v $(pwd):/backup ubuntu tar -czvf /backup/volume-backup.tar.gz
podman run –rm -v mydata:/target -v $(pwd):/backup ubuntu tar -xzvf /backup/volume-backup.tar.gz -C /target
### Best Practices for Data Management:
1. **Use named volumes** for important data rather than anonymous volumes.
2. **Document volume usage** so team members understand what data is stored where.
3. **Back up volumes regularly** to prevent data loss.
4. **Consider volume drivers** for specialized storage needs (e.g., shared storage across nodes).
5. **Separate different types of data** into different volumes for better management.
## 12. Building Custom Container Images: Creating Your Own Templates
There are two main approaches to building custom container images: using Containerfiles (equivalent to Dockerfiles) or using buildah's interactive approach.
### Method 1: Using Containerfiles
A Containerfile is a text file that contains a series of instructions for building an image. Here's a simple example:
```bash
# Create a Containerfile
cat > Containerfile << EOF
FROM ubuntu:latest
RUN apt-get update && apt-get install -y nginx
COPY ./my-nginx.conf /etc/nginx/conf.d/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
EOF
# Build the image
podman build -t my-nginx-image:1.0 .
This Containerfile:
buildah allows you to build images step by step, without needing a Containerfile:
# Create a working container
ctr=$(buildah from ubuntu:latest)
# Install software
buildah run $ctr -- apt-get update
buildah run $ctr -- apt-get install -y nginx
# Set configuration
buildah copy $ctr ./my-nginx.conf /etc/nginx/conf.d/
# Configure the image
buildah config --port 80 $ctr
buildah config --cmd "nginx -g 'daemon off;'" $ctr
# Commit the image
buildah commit $ctr my-nginx-image:1.0
# Remove the working container
buildah rm $ctr
For production images, multi-stage builds can dramatically reduce image size by including only what’s needed for runtime:
cat > Containerfile << EOF
# Build stage
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Runtime stage
FROM ubuntu:22.04
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]
EOF
podman build -t myapp:1.0 .
This approach:
Once you’ve created a custom container image, you may want to save it or share it with others.
If you’ve made changes to a running container (e.g., installed software, modified configuration files), you can save those changes as a new image:
podman commit mycontainer my_custom_ubuntu:v1.0
To share your image with others, you can push it to a container registry:
# Log in to the registry
podman login docker.io
# Tag your image for the registry
podman tag my_custom_ubuntu:v1.0 docker.io/yourusername/my_custom_ubuntu:v1.0
# Push the image
podman push docker.io/yourusername/my_custom_ubuntu:v1.0
You can also save images as archive files for offline transfer:
# Save an image to a tar file
podman save -o my_custom_ubuntu.tar my_custom_ubuntu:v1.0
# Load an image from a tar file
podman load -i my_custom_ubuntu.tar
Containers share the host’s resources, but you can control how much CPU, memory, and other resources each container can use.
# Limit a container to 2 CPU cores and 1GB of memory
podman run --cpus 2 --memory 1g --name resource_limited ubuntu:latest
# Limit a container to specific CPU cores
podman run --cpuset-cpus 0,1 --name cpu_pinned ubuntu:latest
# View real-time resource usage for all containers
podman stats
# View real-time resource usage for a specific container
podman stats mycontainer
# Update memory limits for a running container
podman update --memory 2g mycontainer
# Update CPU limits for a running container
podman update --cpus 4 mycontainer
Container security requires attention at multiple levels: the container runtime, the images, the host, and the network.
# Run a container with a non-root user
podman run --user 1000:1000 ubuntu:latest id
# Run a container with read-only filesystem
podman run --read-only ubuntu:latest
# Drop capabilities to improve security
podman run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
# Scan an image for vulnerabilities (requires Trivy)
trivy image docker.io/library/ubuntu:latest
# Scan a running container
trivy container mycontainer
While podman is excellent for managing individual containers, orchestration tools help manage containers at scale across multiple hosts.
# Install podman-compose
pip3 install podman-compose
# Create a compose file
cat > docker-compose.yml << EOF
version: '3'
services:
web:
image: nginx
ports:
- "8080:80"
db:
image: postgres
environment:
POSTGRES_PASSWORD: example
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
EOF
# Run the multi-container application
podman-compose up -d
# Stop and remove the application
podman-compose down
podman can generate Kubernetes YAML files from containers:
# Generate Kubernetes YAML for a container
podman generate kube mycontainer > mycontainer.yaml
# Create containers from Kubernetes YAML
podman play kube mycontainer.yaml
Read: What is Kubernetes and how does it work
Even with the best setup, you’ll occasionally encounter issues with containers. Here are some common problems and their solutions:
# Check for detailed error messages
podman logs mycontainer
# Inspect container configuration
podman inspect mycontainer
# Try running with more verbose output
podman run --rm -it --log-level=debug ubuntu:latest
# Check container IP address
podman inspect -f '{{.NetworkSettings.IPAddress}}' mycontainer
# Test network from within container
podman exec mycontainer ping -c 3 google.com
# Inspect network configuration
podman network inspect podman
# Check available disk space
df -h
# Inspect volume details
podman volume inspect myvolume
# Clean up unused resources
podman system prune -a
# Check if container is hitting resource limits
podman stats mycontainer
# Increase limits if necessary
podman update --memory 2g --cpus 2 mycontainer
This guide has provided a comprehensive introduction to Linux containers on Ubuntu, covering the key concepts, tools, and techniques. You’ve learned:
Containers continue to evolve rapidly, but the fundamental concepts covered in this guide will remain relevant. By understanding these core principles and practices, you’re well-equipped to adopt containers in your Ubuntu environment and leverage their benefits for your applications and services.
Q: Can I run Windows containers on Ubuntu?
A: No. Linux containers share the Linux kernel. You can’t run Windows applications in a Linux container. To run Windows containers, you need a Windows host (or a Windows VM).
Q: Is podman a complete replacement for Docker?
A: For most common use cases, yes. podman provides a very similar command-line interface to Docker, and it can work with Docker images and registries. However, Docker has some features (like Docker Swarm for orchestration) that podman doesn’t directly replicate. For those features, you’d typically use Kubernetes or Podman’s own podman-compose tool.
Q: What’s the difference between podman and buildah?
A: podman is primarily for running and managing containers. buildah is primarily for building container images. You can use podman to run pre-built images, but if you need to create your own custom images from scratch, buildah gives you more control.
Q: How can I see the IP address of a container?
A: If using the default bridged network (CNI), you can see the assigned IP address using the podman inspect command:
podman inspect -f '{{.NetworkSettings.IPAddress}}' mycontainer
Alternatively, you can run ip addr inside the container:
podman exec mycontainer ip addr
Q: How do I share files between the host and a container?
A: The best way to share files is to use volumes or bind mounts:
# Using a named volume
podman run -v myvolume:/path/in/container ubuntu:latest
# Using a bind mount
podman run -v /path/on/host:/path/in/container ubuntu:latest
Q: Can I run graphical applications inside a container?
A: Yes, but it requires sharing your X11 socket with the container:
podman run -it --net=host -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix ubuntu:latest
You may also need to run xhost +local: on the host before launching the container.
Q: What’s the difference between stopping and pausing a container?
A:
Q: Where are container images stored locally?
A: The exact location depends on the storage driver being used, but it’s typically within /var/lib/containers/ (for rootful podman) or within your user’s home directory (e.g., ~/.local/share/containers/ for rootless podman). You generally don’t interact with these files directly; use podman commands to manage images.
Q: How can I copy files between the host and a container?
A: You can use the podman cp command:
# Copy a file from the host to the container
podman cp my_local_file.txt mycontainer:/path/inside/container/
# Copy a file from the container to the host
podman cp mycontainer:/path/inside/container/remote_file.txt ./
Q: How do I handle log files in containers?
A: Container logs should be directed to stdout/stderr rather than files. podman captures these streams and makes them available through podman logs. For applications that can only log to files, consider using a volume for logs or a logging sidecar container.
Q: How can I clean up unused containers and images to save disk space?
A: Use the system prune command:
# Remove all stopped containers, unused networks, and dangling images
podman system prune
# Also remove all unused images, not just dangling ones
podman system prune -a
Q: How can I ensure my containers are secure?
A: Follow these key practices:
Q: Can containers escape and access the host system?
A: While container isolation is strong, it’s not perfect. Containers with elevated privileges (especially those running as root or with special capabilities) might be able to escape under certain circumstances. This is why following security best practices is crucial.
Q: Can I limit container resource usage?
A: Yes, you can limit CPU, memory, and other resources:
podman run --cpus 2 --memory 1g --name resource_limited ubuntu:latest
Q: How do I update a running container’s configuration?
A: Use the podman update command:
podman update --cpus 4 --memory 2g mycontainer
Q: Can I automatically restart containers if they crash or if the host reboots?
A: Yes, use the –restart flag:
podman run --restart=always myimage
For system-wide persistence, consider using systemd units:
# Generate a systemd unit for a container
podman generate systemd --name mycontainer > ~/.config/systemd/user/container-mycontainer.service
# Enable and start the service
systemctl --user enable --now container-mycontainer
The post The Complete Ubuntu Container Guide: From Basics to Production for System Administrators appeared first on net2.
Official Ubuntu support for the NVIDIA Rubin platform, including the NVIDIA Vera Rubin NVL72 rack-scale…
CES 2026 is here, bringing together the technologies defining the next generation of connected devices.…
Here’s the guide to deploy Elastic Stack on Ubuntu VPS, with secure access, HTTPS proxying,…
This guide walks through deploying Nagios Core on an Ubuntu VPS, from system prep to…
After a decade under Pablo Cantero's stewardship, Shoryuken has a new maintainer - me. I'm…
MAAS 3.7 has been officially released and it includes a bunch of cool new features.…
View Comments