How to Manage Docker Images and Containers on RHEL 7
Effective day-to-day use of Docker requires a solid command of image and container management. Images are the read-only blueprints from which containers are created; containers are the running (or stopped) instances of those images. Over time, a Docker host accumulates pulled images, stopped containers, dangling build layers, and unused volumes that consume disk space. This tutorial covers the complete lifecycle of Docker images and containers on RHEL 7: pulling from registries, building your own images with a Dockerfile, running and interacting with containers, inspecting their state and logs, and cleaning up resources when they are no longer needed.
Prerequisites
- Docker CE installed and running on RHEL 7
- Your user added to the
dockergroup (or usesudofor all commands) - Basic familiarity with the Linux command line
- Internet access to pull images from Docker Hub
Step 1: Pull Images from a Registry
The docker pull command downloads an image from a registry (Docker Hub by default) to the local image cache stored at /var/lib/docker/.
Pull the latest official Nginx image:
docker pull nginx
Pull a specific tag (always prefer explicit tags in production over latest):
docker pull nginx:1.25-alpine
Pull from a private registry:
docker pull registry.example.com/myteam/myapp:v2.1.0
To log in to a private registry first:
docker login registry.example.com
Step 2: List and Inspect Local Images
List all locally available images:
docker images
Or equivalently:
docker image ls
Sample output:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx 1.25-alpine a8758716bb6a 2 weeks ago 41.4MB
nginx latest 7f3b4b55dd4f 2 weeks ago 187MB
python 3.11-slim 6a5e6a44b8e2 3 weeks ago 130MB
Inspect the detailed metadata of an image (layers, environment variables, entrypoint, etc.):
docker image inspect nginx:1.25-alpine
Step 3: Remove Images
Remove a specific image by repository:tag or by image ID:
docker rmi nginx:latest
Or using the long-form command:
docker image rm nginx:latest
If a container (even a stopped one) is using the image, Docker will refuse to remove it unless you force it with -f:
docker rmi -f nginx:latest
Remove all dangling images (untagged layers left over from builds):
docker image prune
Remove all unused images (not just dangling ones):
docker image prune -a
Step 4: Write a Dockerfile
A Dockerfile is a text file containing a sequence of instructions that Docker reads top to bottom to assemble an image layer by layer. Create a working directory and a simple Python web application:
mkdir -p ~/myapp
cd ~/myapp
Create the application file:
cat > app.py <<'EOF'
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"Hello from RHEL 7 Docker!")
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
EOF
Create the Dockerfile:
cat > Dockerfile <<'EOF'
# Base image — always pin to a specific version
FROM python:3.11-slim
# Set a non-root user for security
RUN useradd --create-home appuser
# Set working directory
WORKDIR /app
# Copy application source
COPY app.py .
# Declare the port the application listens on (documentation only)
EXPOSE 8000
# Switch to non-root user
USER appuser
# Define the command to run when the container starts
CMD ["python", "app.py"]
EOF
Key Dockerfile instructions explained:
- FROM: Specifies the base image. Every Dockerfile must start with
FROM. - RUN: Executes a shell command during the image build and creates a new layer. Use
&&to chain commands and minimize layers. - COPY: Copies files from the build context (your local directory) into the image.
- WORKDIR: Sets the working directory for subsequent
RUN,COPY,CMD, andENTRYPOINTinstructions. - EXPOSE: Documents which port the container listens on. It does not actually publish the port —
-pat runtime does that. - CMD: The default command to execute when the container starts. Can be overridden at
docker runtime.
Step 5: Build an Image with docker build
Build the image from the Dockerfile in the current directory, tagging it with a name and version:
docker build -t myapp:1.0 .
The . at the end tells Docker to use the current directory as the build context. To use a different Dockerfile name or location:
docker build -t myapp:1.0 -f /path/to/Dockerfile.prod /path/to/context
To see the layers and build cache usage:
docker build --no-cache -t myapp:1.0 .
Verify the image was created:
docker images myapp
Step 6: Run, Stop, and Start Containers
Run the newly built image as a container in detached mode with port publishing:
docker run -d -p 8000:8000 --name myapp_container myapp:1.0
Test the running container:
curl http://localhost:8000
List all running containers:
docker ps
List all containers including stopped ones:
docker ps -a
Stop a running container (sends SIGTERM, then SIGKILL after 10 seconds):
docker stop myapp_container
Start a previously stopped container:
docker start myapp_container
Restart a container (useful after configuration changes):
docker restart myapp_container
Remove a stopped container:
docker rm myapp_container
Force-remove a running container (sends SIGKILL immediately):
docker rm -f myapp_container
Step 7: Execute Commands Inside Running Containers
The docker exec command runs a new process inside an already-running container without stopping it. The most common use case is opening an interactive shell for debugging:
docker exec -it myapp_container /bin/sh
Use /bin/bash if the container image includes bash (Alpine-based images typically only have /bin/sh):
docker exec -it myapp_container /bin/bash
Run a one-off command without an interactive shell:
# Check a configuration file inside the container
docker exec myapp_container cat /etc/nginx/nginx.conf
# List processes inside the container
docker exec myapp_container ps aux
# Check environment variables
docker exec myapp_container env
Step 8: View Container Logs
Fetch the logs output from a container (stdout and stderr from PID 1):
docker logs myapp_container
Follow logs in real time (like tail -f):
docker logs -f myapp_container
Show only the last N lines:
docker logs --tail 100 myapp_container
Show logs with timestamps:
docker logs -t myapp_container
If you configured the journald log driver in /etc/docker/daemon.json, you can also query container logs via journalctl:
sudo journalctl CONTAINER_NAME=myapp_container -f
Step 9: Inspect Container Details
The docker inspect command returns a JSON object with the complete configuration and runtime state of a container or image:
docker inspect myapp_container
Extract specific fields using Go template format with the --format flag:
# Get the container's IP address
docker inspect --format '{{ .NetworkSettings.IPAddress }}' myapp_container
# Get the container's restart policy
docker inspect --format '{{ .HostConfig.RestartPolicy.Name }}' myapp_container
# Get mount points
docker inspect --format '{{ json .Mounts }}' myapp_container | python -m json.tool
Step 10: Clean Up with docker system prune
Over time, Docker accumulates stopped containers, unused images, build cache, and unused networks. The docker system prune command removes all of these in one step.
Preview what would be removed (dry run):
docker system df
Sample output showing disk usage:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 4 2.341GB 1.89GB (80%)
Containers 8 2 145MB 132MB (91%)
Local Volumes 5 2 890MB 340MB (38%)
Build Cache 0 0 0B 0B
Remove stopped containers, unused networks, dangling images, and build cache:
docker system prune
Include unused images (not just dangling layers) and unused volumes:
docker system prune -a --volumes
Warning: The --volumes flag will delete named volumes not attached to any running or stopped container. Ensure you have backed up any important data first.
For more granular cleanup, use targeted commands:
# Remove all stopped containers
docker container prune
# Remove dangling images only
docker image prune
# Remove unused networks
docker network prune
# Remove unused volumes
docker volume prune
Conclusion
You now have a comprehensive toolkit for managing Docker images and containers on RHEL 7. From pulling and building images to running, inspecting, debugging, and cleaning up containers, these commands cover the full lifecycle of containerized workloads. Writing clean, layered Dockerfiles with pinned base image versions and non-root users is essential for producing secure, reproducible images. Regular use of docker system df and docker system prune keeps your host’s disk usage under control. As your container usage grows, combine these fundamentals with Docker Compose for multi-service applications and Docker Swarm or Kubernetes for production-grade orchestration.