Docker has become a standard tool for developers and system administrators who need to package, distribute, and run applications consistently across different environments. If you run Ubuntu and want to start using Docker, this guide walks through the complete installation process, explains the core concepts, and shows practical examples you can use immediately after setting things up.
The installation method covered here uses the official Docker repository. This approach gives you the latest stable release rather than relying on the version available in Ubuntu's default repositories, which is often several versions behind.
What Docker Actually Is
Docker packages applications and their dependencies into containers: lightweight, standalone units that include everything needed to run the software. A container is not a virtual machine. It runs on the host kernel, shares the host operating system, and starts in seconds rather than minutes. Multiple containers can run on the same host without interfering with each other.
The key benefit is consistency. If your application runs in a container on your development machine, it runs the same way on a testing server and in production. The "it works on my machine" problem largely disappears when everyone works with the same container image.
Docker Compose complements Docker by managing multi-container applications. Rather than running multiple docker run commands separately, you define your entire stack in a single configuration file and control it with straightforward commands. You can explore more about multi-container setups in this Docker Compose guide.
Why Install Docker from the Official Repository
Ubuntu includes Docker packages in its default repositories, but these versions often lag behind the current stable releases. Installing from the official Docker repository ensures you get the latest features, security updates, and bug fixes. The process takes a few extra minutes but pays off in long-term stability and access to newer capabilities.
The official repository also includes docker-compose-plugin, which integrates Docker Compose directly into the Docker CLI rather than requiring a separate installation.
Installing Docker on Ubuntu
The installation involves adding Docker's GPG key, adding the repository, and then installing the packages. Run each command in sequence.
sudo apt update
sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
These packages enable secure package transfers over HTTPS and provide tools needed for the repository setup.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
This downloads Docker's GPG key and converts it to a format Ubuntu's package manager can use to verify package authenticity.
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
This adds Docker's official repository to your system. The signed-by option links the repository to the GPG key you just installed.
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
The docker-ce package is the Docker Engine itself. The containerd component handles container lifecycle management, and the compose plugin provides Docker Compose functionality.
Adding Your User to the Docker Group
By default, running Docker commands requires root privileges. You can add your user to the docker group to avoid typing sudo for every command.
sudo usermod -aG docker $USER
newgrp docker
The first command adds your current user to the docker group. The second command refreshes your group memberships so you do not need to log out and back in. After running these commands, verify your access by running a test command.
docker run hello-world
If Docker prints a message saying your installation appears to be working correctly, you are ready to proceed.
Note: Members of the docker group can effectively gain root access on the host system. Only add users you trust with that level of access. For shared systems, prefer using sudo docker instead of adding users to the docker group.
Verifying the Installation
Check the installed Docker version to confirm everything is set up correctly.
docker --version
You should see output similar to "Docker version 24.0.7, build afdd53b" or whatever current version is installed. The docker-compose-plugin version can be checked separately.
docker compose version
Understanding Docker Images and Containers
A Docker image is a read-only template with instructions for creating a container. Images are defined by a Dockerfile, which specifies the base image, the application code, dependencies, and configuration.
A container is a runnable instance of an image. You can run multiple containers from the same image, and they remain isolated from each other and from the host system. This isolation is what makes Docker useful for running different versions of the same software on one machine without conflicts.
Basic commands for working with images and containers include:
# List running containers
docker ps
# List all containers (including stopped)
docker ps -a
# List downloaded images
docker images
Running Your First Container
The simplest way to run a container is to use an existing image from Docker Hub, which is the public image registry maintained by Docker. Running an official image is a good way to verify your installation and understand how containers behave.
docker run nginx:alpine
This pulls the nginx:alpine image from Docker Hub if it is not already present, then starts a container from it. The container runs in the foreground, printing logs to your terminal. Press Ctrl+C to stop it.
Running containers in detached mode (background) is more practical for server use. You can also map ports to access services running inside the container from your host system.
docker run -d --name my_nginx -p 8080:80 nginx:alpine
This runs the container in the background with the name my_nginx. The -p flag maps port 80 inside the container to port 8080 on the host. Visit http://localhost:8080 to see the Nginx welcome page.
Managing running containers involves a few straightforward commands:
# View running containers
docker ps
# Stop the container
docker stop my_nginx
# Remove the container (after stopping)
docker rm my_nginx
Creating a Dockerfile for a PHP Application
A Dockerfile defines how to build your own application image. For a PHP web application served by Nginx, you typically need two Dockerfiles: one for the PHP service and one for Nginx.
Here is a basic Dockerfile for a PHP application using the official PHP FPM image:
FROM php:8.2-fpm-alpine
RUN docker-php-ext-install pdo pdo_mysql
COPY src/ /var/www/html/
RUN chown -R www-data:www-data /var/www/html
This Dockerfile starts from the official PHP Alpine image, installs the PDO and MySQL extensions, copies your application code into the container, and sets the correct ownership.
And the Nginx Dockerfile:
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY public/ /var/www/html/
Build both images with the docker build command:
docker build -t myapp-php ./php
docker build -t myapp-nginx ./nginx
The -t flag tags the image with a name, making it easier to reference later.
Using Docker Compose for Multi-Container Applications
Docker Compose manages multi-container applications through a single configuration file. A docker-compose.yml file defines the services, networks, and volumes for your application stack. This approach simplifies running complex setups that would otherwise require multiple docker run commands with many flags.
For a typical web application with Nginx, PHP, and MySQL, the compose file looks like this:
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./public:/var/www/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
php:
image: php:8.2-fpm-alpine
volumes:
- ./public:/var/www/html
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
Start the entire stack with one command:
docker-compose up -d
Useful Docker Compose commands for managing your stack:
# View logs from all services
docker-compose logs -f
# Stop everything
docker-compose down
# Stop and remove volumes (fresh start)
docker-compose down -v
Managing Data with Docker Volumes
Containers are ephemeral by design. When a container is removed, any data stored inside it disappears. For databases and other data that must persist, use Docker volumes. Volumes store data outside the container's filesystem, ensuring it survives container removal and updates.
# Create a named volume
docker volume create mydata
# Run a container with a volume
docker run -v mydata:/data nginx:alpine
# Inspect volume details
docker volume inspect mydata
In docker-compose, volumes are defined under the top-level volumes key and referenced by name in each service:
volumes:
db_data:
driver: local
The named volume db_data is then mounted to the MySQL service:
services:
db:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql
Networking Between Containers
Docker Compose automatically creates a network for your services. All containers defined in the compose file can communicate with each other using their service names as hostnames.
In the example above, the PHP service can reach the MySQL service at the hostname db on port 3306. Your application configuration in PHP should use db as the database hostname, not localhost.
# In your PHP application configuration
$dbHost = 'db';
$dbName = 'myapp';
$dbUser = 'root';
$dbPass = 'secret';
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName", $dbUser, $dbPass);
This is a common point of confusion when moving applications into Docker. The container running your application has its own network namespace and cannot reach localhost on the host machine. Using the service name from your compose file is the correct approach.
Useful Docker Commands for Daily Use
Once you have Docker running, a handful of commands cover most daily operations:
# See resource usage for all containers
docker stats
# See all containers regardless of state
docker ps -a
# View logs from a specific container
docker logs -f container_name
# Execute a command inside a running container
docker exec -it container_name /bin/sh
# Copy files between host and container
docker cp local_file.txt container_name:/path/
docker cp container_name:/path/file.txt local_file.txt
# Remove stopped containers, unused networks, and dangling images
docker system prune
The docker exec command is particularly useful for debugging. If a container is not behaving as expected, you can open a shell inside it and inspect the filesystem, running processes, and configuration.
Container Security Considerations
Running containers in production requires attention to security. Containers share the host kernel, so vulnerabilities in container images can affect the entire host. A few practical steps help reduce risk.
Use official images from trusted sources whenever possible. Official images are maintained by the software vendors or Docker's own team and receive regular security updates. Third-party images may not be updated as frequently.
Avoid running containers as root when possible. Many official images include a non-root user specifically for this purpose. If your image does not include one, consider creating a Dockerfile that adds a user.
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Keep images updated. When new versions of base images become available, rebuild your custom images to incorporate security patches. You can learn more about hardening containers in this Docker security guide.
When Docker Might Be Overkill
Docker is powerful but introduces complexity. For a simple single-server setup with one or two applications, traditional installation methods are often simpler and easier to debug. You have direct access to the filesystem, straightforward log locations, and no additional abstraction layer to reason about.
If you run a single WordPress site on a VPS, a traditional LAMP setup is likely easier to manage than containerising WordPress, Nginx, MySQL, and PHP separately. You gain simplicity at the cost of some flexibility and environment consistency.
Docker becomes valuable as the complexity of your infrastructure grows. If you need to run multiple applications with conflicting dependencies, want to scale horizontally across multiple servers, or need to replicate a production environment locally for development, Docker handles these scenarios well. You might also consider pairing Docker with a reverse proxy setup, which you can explore in this Nginx reverse proxy guide.
Taking the Next Step with Docker
Installing Docker on Ubuntu is straightforward once you understand the repository setup and core concepts. The official Docker documentation provides detailed references for each command and configuration option if you need to go deeper into specific topics.
If you are planning to expose containerised services to the internet, a reverse proxy configuration helps manage multiple domains and automatically handles SSL certificates. Combining Docker with proper server security practices keeps your applications both accessible and protected.
If you encounter issues during installation or need help designing a containerised architecture for your project, you can get in touch with details about your setup, the applications you want to run, and your current server environment.