civil-and-structural-engineering
Deploying Docker Containers with Systemd on Centos and Rhel Systems
Table of Contents
Introduction
Managing Docker containers as system services on CentOS and RHEL systems offers a robust, native approach to container orchestration. By integrating containers into systemd, you gain automatic startup on boot, integrated logging via journald, and straightforward lifecycle management. This article provides a comprehensive guide to deploying and managing Docker containers with systemd, including best practices, advanced configuration, and troubleshooting techniques.
Systemd is the default init system on modern CentOS and RHEL distributions (7 and later). It provides a dependency-based service manager that can handle Docker containers just like any other system service. This approach is particularly useful for production environments where reliability, monitoring, and consistent restart behavior are critical.
Prerequisites
- A CentOS or RHEL system (version 7 or later) with root or sudo access.
- Docker Engine installed and the Docker daemon running.
- Familiarity with basic command-line operations, systemd units, and Docker commands.
- Network connectivity to pull container images from a registry (e.g., Docker Hub).
Installing Docker on CentOS and RHEL
If Docker is not already present, install it using the official repository. The steps below work for CentOS 7/8 and RHEL 7/8 (for RHEL 9, adjust the repository URL accordingly).
Step 1: Install Required Packages
First, install yum-utils to manage repositories:
sudo yum install -y yum-utils
Step 2: Add the Docker Repository
Add the official Docker CE repository:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
For RHEL systems, you may need to enable the rhel-7-server-extras-rpms or rhel-8-for-x86_64-baseos-rpms repository depending on your subscription. Alternatively, you can use the CentOS repository as shown above, but verify compatibility.
Step 3: Install Docker Engine
sudo yum install -y docker-ce docker-ce-cli containerd.io
Step 4: Start and Enable Docker
sudo systemctl start docker
sudo systemctl enable docker
Verify the installation:
sudo docker run hello-world
For more details, refer to the official Docker installation guide for CentOS.
Creating a Systemd Service for a Docker Container
Each container managed by systemd requires a dedicated unit file. This file defines how the container is started, stopped, and restarted. Below we create a service for an Nginx container.
Step 1: Create the Unit File
Create a file named /etc/systemd/system/nginx-docker.service:
sudo nano /etc/systemd/system/nginx-docker.service
Step 2: Define the Unit
Add the following content:
[Unit]
Description=Nginx Docker Container
After=docker.service
Requires=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker run --rm --name nginx-container -p 80:80 nginx:latest
ExecStop=/usr/bin/docker stop nginx-container
ExecStartPre=-/usr/bin/docker rm nginx-container
[Install]
WantedBy=multi-user.target
Explanation of the unit file:
- [Unit]: Describes the service and declares dependencies.
After=docker.serviceensures Docker is running before the container starts.Requires=docker.servicemakes Docker a hard dependency. - [Service]: Defines the runtime behavior.
Restart=alwaysauto-restarts the container on exit (e.g., crashes).ExecStartlaunches the container;--rmremoves the container after it stops (useful for ephemeral containers).ExecStopsends a graceful stop to the container.ExecStartPrecleans up any leftover container with the same name before starting. - [Install]: Sets the target that enables the service to start at boot.
multi-user.targetis the standard runlevel for non-graphical systems.
Note the use of the full path to the docker binary. Always specify absolute paths in systemd units to avoid PATH-related issues.
Step 3: Reload systemd and Start the Service
sudo systemctl daemon-reload
sudo systemctl enable nginx-docker
sudo systemctl start nginx-docker
Check the status:
sudo systemctl status nginx-docker
Managing the Service
Once the unit is active, you can control the container like any other systemd service:
- Start/Stop/Restart:
sudo systemctl start|stop|restart nginx-docker - Enable/Disable on boot:
sudo systemctl enable|disable nginx-docker - View logs:
sudo journalctl -u nginx-docker - Follow logs in real time:
sudo journalctl -u nginx-docker -f
If you need to update the container image, stop the service, modify the ExecStart line to use a new image tag, reload the daemon, and start again. For zero-downtime updates, consider using a reverse proxy like Nginx with dynamic upstreams.
Advanced Configuration
The basic unit file can be extended to meet production requirements. Below are several advanced patterns.
Restart Policies and Backoff
Systemd’s Restart=always can cause a restart loop if a container fails immediately. To mitigate this, add RestartSec=5 (or higher) to introduce a delay between restarts:
Restart=always
RestartSec=5
For containers that should restart only on failure (not on clean exit), use Restart=on-failure. You can also set StartLimitIntervalSec=600 and StartLimitBurst=5 to prevent excessive restarts.
Resource Limits via systemd
Systemd can enforce CPU and memory limits using cgroups. Add the following to the [Service] section:
MemoryMax=512M
CPUQuota=50%
These settings limit the container to 512 MB of memory and 50% of a single CPU core. This is often simpler than using Docker’s native resource constraints (--memory, --cpus) and ensures limits are applied even if the container is restarted outside Docker.
Environment Variables
Pass environment variables to the container using Environment or EnvironmentFile:
Environment="MY_VAR=value"
EnvironmentFile=-/etc/containers/nginx.env
The leading - in EnvironmentFile means the file is optional; systemd will not fail if it is missing. This is useful for environment-specific configurations.
Logging and Journald Integration
By default, systemd captures stdout/stderr from the container and stores them in the journal. You can adjust log handling with StandardOutput and StandardError directives. For example, to log to a file instead:
StandardOutput=file:/var/log/nginx-container.log
StandardError=file:/var/log/nginx-container-error.log
Be cautious when using file logging; journald already provides robust log rotation via journalctl. For long-running containers, consider setting LogRateLimitIntervalSec=30 to prevent log flooding.
Health Checks
While systemd does not have built-in health check functionality, you can combine ExecStartPost with a script that verifies the container is responsive. For instance:
ExecStartPost=/usr/local/bin/wait-for-it.sh localhost:80 --timeout=30
If the script fails (non-zero exit), systemd marks the service as failed. For more sophisticated health monitoring, use Docker’s built-in health check inside the container and parse it with a custom systemd ExecStopPost script.
Monitoring and Troubleshooting
Checking Service Status
Use systemctl status to see whether the container is running, how long it has been up, and recent log lines:
sudo systemctl status nginx-docker
Look for the Active line. A healthy service shows active (running).
Viewing Container Logs
Journald stores logs with the unit name. To see all logs:
sudo journalctl -u nginx-docker
To see logs from the last hour:
sudo journalctl -u nginx-docker --since "1 hour ago"
If the container uses Docker’s logging driver (e.g., json-file), you can also view logs with docker logs nginx-container. However, journald integration is preferred for consistency with system services.
Common Issues and Solutions
- Service fails to start, “Container is already in use”: Add
ExecStartPre=-/usr/bin/docker rm nginx-container(note the leading-to ignore errors if the container does not exist). - Docker daemon not ready when systemd tries to start the container: Ensure
After=docker.serviceandRequires=docker.serviceare set. You might also addBindsTo=docker.serviceto stop the container if Docker stops. - Permission denied for Docker commands: The systemd service runs as root by default. If you change the user with
User=, that user must be in thedockergroup or have equivalent socket permissions. For security, avoid running the service as a non-root user unless you have configured rootless Docker. - High disk usage from journal logs: Configure log rotation in
/etc/systemd/journald.conf(e.g.,SystemMaxUse=500M).
Comparison with Docker-Compose and Systemd Slices
For multi-container deployments, Docker Compose is often more convenient, but it does not integrate as tightly with systemd. A common pattern is to create a systemd service that runs docker-compose up and another that runs docker-compose down. Alternatively, you can create individual systemd services for each container and group them using systemctl enable --now with a target.
For advanced resource isolation, systemd slices can group related services (e.g., service.slice) and apply unified resource limits. Example:
sudo systemctl set-property nginx-docker.service MemoryMax=256M
This applies the limit at runtime without editing the unit file.
Security Considerations
Running containers with systemd does not inherently compromise security, but you should follow best practices:
- Use read-only root filesystems when possible: add
--read-onlyto thedocker runcommand. - Drop Linux capabilities:
--cap-drop=ALL --cap-add=NET_BIND_SERVICEfor the Nginx example. - Run containers as a non-root user inside the container (e.g.,
--user nginx). - Use systemd’s
PrivateTmp=true,NoNewPrivileges=true, andProtectSystem=strictin the service unit to harden the service itself (though these affect the Docker CLI process, not the container).
For more details, see Docker security best practices and the systemd.exec man page.
Conclusion
Deploying Docker containers with systemd on CentOS and RHEL systems provides a reliable, well-integrated method for managing containerized applications as native services. By creating custom unit files, you can control container lifecycle, enforce resource limits, and leverage systemd’s logging and monitoring capabilities. The patterns presented in this article are production-ready and can be adapted to virtually any container workload. Start by creating simple unit files for your most critical containers, then expand with advanced configurations as your infrastructure grows.
For further reading, consult the systemd.service documentation and the Docker restart policies guide.