Working with Docker Images and Registries
Let's start with a common container creation command:
docker run -i -t --name custom_centos7 centos:7 /bin/bash
This spins up a new container named custom_centos7 from the CentOS 7 base image and drops you into an interactive Bash shell.
What is a Docker Image?
Docker images are constructed from a stacked layered filesystem:
- The bottom layer is the boot filesystem (
bootfs), which handles system bootstrapping - The next layer is the root filesystem (
rootfs), which is mounted read-only. Docker uses union mount technology to stack multiple read-only filesystems on top of each other
This stacked filesystem structure is what Docker calls an image. Images are fully stackable, and only the topmost layer is writable — all lower layers remain read-only.
List Local Docker Images
Use the docker images command to view all images stored on your local system:
sudo docker images
Example output:
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d2c94e258dcb 11 months ago 13.3kB
centos 7 eeb6ee3f44bd 2 years ago 204MB
By default, Docker stores image files locally in the /var/lib/docker directory.
To download a new image from a remote registry, use the pull command:
sudo docker pull centos:8
After pulling, you'll see the new tagged image in your local list:
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d2c94e258dcb 11 months ago 13.3kB
centos 7 eeb6ee3f44bd 2 years ago 204MB
centos 8 5d0da3dc9764 2 years ago 231MB
Pulling Images
When you use docker run to start a container, Docker will automatically pull the image from Docker Hub (the default public registry) if it doesn't exist locally. If you don't specify a tag, Docker will default to pulling the latest tag.
You can filter the output of docker images to only show images from a specific repository:
docker images centos
Output:
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7 eeb6ee3f44bd 2 years ago 204MB
centos 8 5d0da3dc9764 2 years ago 231MB
To pull a specific tagged version of an image, specify the tag as shown below:
sudo docker pull centos:8
Search for Public Images
Use the docker search command to find public images hosted on Docker Hub:
docker search mysql
Example output:
NAME DESCRIPTION STARS OFFICIAL
mariadb MariaDB Server is a high performing open sou… 5716 [OK]
mysql MySQL is a widely used, open-source relation… 14989 [OK]
percona Percona Server is a fork of the MySQL relati… 627 [OK]
phpmyadmin phpMyAdmin - A web interface for MySQL and M… 964 [OK]
The output includes four columns: repository name, image description, star count (user rating), and an indicator for official images.
Building Custom Docker Images
There are two common methods to create and manage your own Docker images:
- The
docker commitcommand - The
docker buildcommand paired with a Dockerfile
The Dockerfile method is the current industry standard, but we cover both approaches below.
Authenticate with Docker Hub
First, create an acccount on Docker Hub. You can register directly or use an existing GitHub/Google account, but you will need to set an account password for CLI login.
Use the docker login command to authanticate your local CLI:
sudo docker login
Example successful output:
Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.
You can log in with your password or a Personal Access Token (PAT). Using a limited-scope PAT grants better security and is required for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/
Username: your_username
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
You can use docker logout to sign out of your account at any time.
Create an Image with docker commit
The docker commit command works similar to committing changes in version control: you modify a running container, then save those changes as a new image layer.
First, start a new base container:
docker run -i -t centos:7 /bin/bash
Inside the container, install Apache HTTP server:
yum install httpd
Exit the container with exit, then run docker ps -a to get the container ID, then commit the changes to a new image:
docker commit <container-id> your_username/httpd-test
Example output:
sha256:7b2dde3fde6ed893914216d5693e305c7bdc47934879a3f0c4da5cc0e5fe27a8
You can verify the new image exists with docker images:
REPOSITORY TAG IMAGE ID CREATED SIZE
your_username/httpd-test latest 7b2dde3fde6e 18 minutes ago 885MB
You can add commit metadata and author information with additional flags:
# -m = commit message, -a = author name, final argument sets tagged version
docker commit -m"Added httpd server" -a"Your Name" 2cdeb9fa7543 your_username/httpd:prod
Start a new container from your custom image with:
docker run -t -i your_username/httpd:prod
Create an Image with a Dockerfile
The recommended approach is to use a Dockerfile, a text file that defines all steps to build your image. Start by creating a new working directory and an empty Dockerfile:
mkdir custom-nginx-repo
cd custom-nginx-repo
touch Dockerfile
This directory is your build context, the root environment Docker uses to build your image.
Edit the Dockerfile with the following content:
# Base image to build from
FROM centos:7
# Maintainer information
MAINTAINER Your Name <you@example.com>
# Install Nginx
RUN yum -y install epel-release && \
yum -y update && \
yum -y install nginx
# Add custom default landing page
COPY custom-index.html /usr/share/nginx/html/index.html
# Expose container port 80 for incoming traffic
EXPOSE 80
Create the custom-index.html file in your build context with the following content (note the charset declaration to avoid garbled non-ASCII text):
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
</head>
<body>
Hello from my custom Docker image!
你好,这是我的自定义 Nginx 镜像
</body>
</html>
Optionally, you can add a .dockerignore file to exclude files from being added to the build context:
.git
venv
*.log
Now build the image with the docker build command, using the -t flag to tag the image with your repository name:
docker build -t="your_username/nginx-demo" .
Example build output:
[+] Building 78.0s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 288B 0.0s
=> [internal] load metadata for docker.io/library/centos:7 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/2] FROM docker.io/library/centos:7 0.0s
=> [2/2] RUN yum -y install epel-release && yum -y update && yum -y install nginx 73.5s
=> exporting to image 4.3s
=> => exporting layers 4.2s
=> => writing image sha256:7305f86bc0d28253df007343fc302c375d434602fef502b5ceaf118a98a71ba5 0.0s
=> => naming to docker.io/your_username/nginx-demo 0.0s
Check the built image:
docker images
Output:
REPOSITORY TAG IMAGE ID CREATED SIZE
your_username/nginx-demo latest 7305f86bc0d2 About a minute ago 659MB
Note the dot (.) at the end of the build command — it tells Docker the Dockerfile is in your current working directory. You can also build from a remote git repository:
docker build -t="your_username/nginx-demo" git@github.com:your-username/dockerfile-repo.git
Or specify a custom path to your Dockerfile with the -f flag:
docker build -t="your_username/nginx-demo" -f ~/projects/my-docker/Dockerfile .
Build Cache and Troubleshooting
If a build fails, Docker will show you exactly which step failed. When you re-run a build after fixing errors, Docker will reuse cached layers from all previously successful steps to speed up builds. To force a full rebuild without cache, add the --no-cache flag:
docker build --no-cache -t="your_username/nginx-demo" .
Run a Container from Your Custom Image
You can inspect the layer history of your new image with the docker history command:
docker history 7305f86bc0d2
Output will show you every layer and the command that created it.
Start a new detached container from your image with port mapping:
# -d = run in background, -p = map host port to container port
docker run -d -p 80 --name custom-nginx your_username/nginx-demo nginx -g "daemon off;"
If you don't specify a host port, Docker will randomly assign a high port between 32768 and 61000. You can check the mapped port with either docker ps -l or docker port:
docker port custom-nginx 80
Output:
0.0.0.0:32768
[::]:32768
To bind a specific host port, use one of the following formats:
# Bind all interfaces on host port 80 to container port 80
docker run -d -p 80:80 \
--name custom-nginx your_username/nginx-demo nginx -g "daemon off;"
# Bind only localhost port 80 to container port 80 for local testing
docker run -d -p 127.0.0.1:80:80 --name custom-nginx your_username/nginx-demo nginx -g "daemon off;"
Useful Additional Dockerfile Instructions
Here are other common Dockerfile instructions you will use frequently:
CMD: Specifies the default command to run when a container starts. This will be overridden if you specify a custom command when runningdocker runWORKDIR: Set the default working directory for subsequent commands and running containersENV: Set environment variables inside the imageUSER: Specify the user that should run commands and the container processCOPY: Copy files from your local build context into the image. If the target path does not exist, Docker will create it automatically.
Push Your Image to Docker Hub
Once you've built your image, you can push it to Docker Hub using the docker push command. First, tag your image with your Docker Hub username and repository name:
docker tag your_username/nginx-demo your_username/nginx-demo:1.0.0
Then push the tagged image:
docker push your_username/nginx-demo:1.0.0
If the repository does not exist on Docker Hub, Docker will automatically create it for you.
Delete Local Images
Use docker rmi (remove image) to delete a local image:
docker rmi your_username/httpd:prod
If any existing container is using the image, you will get an error:
Error response from daemon: conflict: unable to remove repository reference "your_username/httpd:prod" (must force) - container 6d00a319a9e8 is using its referenced image 8ce4bf297625
You need to delete the dependent container first with docker rm <container-id> before deleting the image. Docker deletes images one layer at a time, as shown in the output:
Untagged: your_username/httpd:prod
Deleted: sha256:8ce4bf297625ff3693db4e89acb515824f27b391f2e4b3e7e8784beb2fce4cb0
Deleted: sha256:06d5068d22b6af1ad81c235a57579125c1770b4e030c376c7eebd1ba57a57e58
This only deletes the local copy of the image; you need to delete the repository through the Docker Hub web interface if you want to remove it remotely.
To delete all local Docker images at once, run:
docker rmi `docker images -a -q`
Host Your Own Private Docker Registry
All images pushed to public Docker Hub are visible to everyone. If you need a private registry, you have two options:
- Use Docker Hub's paid private repository feature
- Host your own private registry on your infrastructure
To spin up your own registry, use the official registry image from Docker:
docker run -d -p 5000:5000 registry
After starting, you can verify it's working by visiting http://<your-server-ip>:5000/v2/, which will return an empty {} response if running correctly.
Use Your Private Registry
First, pull an image to test with:
docker pull nginx:1.24
Tag the image to point to your private registry:
docker tag nginx:1.24 192.168.10.15:5000/test-nginx:1.24
Push the tagged image to your registry:
docker push 192.168.10.15:5000/test-nginx:1.24
Fix HTTP Push Errors
By default, Docker expects all registries to use HTTPS. Self-hosted registries use HTTP by default, so you will get a push error unless you configure Docker to allow insecure HTTP access to your registry.
To fix this, edit (or create if it doesn't exist) the Docker daemon config at /etc/docker/daemon.json and add your registry to the insecure-registries list:
{
"insecure-registries":[
"192.168.10.15:5000"
]
}
Restart the Docker service to apply the change:
service docker restart
Once Docker restarts, you should be able to push successfully.
Verify and Pull from Your Private Registry
To list all tags for an image in your registry, visit:
http://192.168.10.15:5000/v2/test-nginx/tags/list
You will get a response like:
{"name":"test-nginx","tags":["1.24"]}
To list all repositories in your registry:
http://192.168.10.15:5000/v2/_catalog
Response:
{"repositories":["test-nginx"]}
Delete the local copy of the image to test pulling from your private registry:
docker rmi nginx:1.24
docker rmi 192.168.10.15:5000/test-nginx:1.24
Pull the image back from your private registry:
docker pull 192.168.10.15:5000/test-nginx:1.24