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 docker group (or use sudo for 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, and ENTRYPOINT instructions.
  • EXPOSE: Documents which port the container listens on. It does not actually publish the port — -p at runtime does that.
  • CMD: The default command to execute when the container starts. Can be overridden at docker run time.

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.