Integrating Docker Containers with systemd on Linux: Running Containers as Real Services

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Why Use systemd When Docker Already Has a Restart Policy?

When I first started setting up lab environments, I was convinced that adding --restart always to my docker run command — or declaring restart: always in Docker Compose — was all I needed. If a container crashed, Docker would bring it back. If the server rebooted, the Docker daemon would restart the containers. Seemed fine. But in production, reality is a bit more complicated.

I once had a server lose power unexpectedly. When it came back up, the Docker daemon started before the network-attached storage (NFS) volumes had finished mounting. The result: containers failed to start because their data wasn’t there yet. Another time, I needed an application container to start only after a database installed directly on the host OS — not in Docker — was ready. For scenarios like these, Docker’s Restart Policy is essentially powerless.

That’s what pushed me toward using systemd to manage Docker containers. Systemd is the default system manager on most modern Linux distributions (Ubuntu, Debian, CentOS, and so on). By integrating Docker into systemd, containers are managed like any other OS service — with startup ordering, centralized logging via journald, and much easier monitoring.

Comparing Startup Management Methods

Feature Docker Restart Policy Docker Compose Systemd Integration
Ease of use Very high High Medium
Dependency management None Only within the Compose file Very powerful (works with any OS service)
Auto-restart Yes Yes Yes (with more options)
Logging Docker logs Docker logs Journald (centralized)

Advantages of systemd

  • Startup ordering (Dependencies): You can require a container to start only after the network, MySQL, or a specific disk is ready.
  • Intelligent recovery: Systemd offers smarter restart behavior — for example, retry 5 times with 10 seconds between attempts, then stop entirely to avoid infinite crash loops.
  • Monitoring integration: Tools like Zabbix and Prometheus can easily check service status via systemctl.

Disadvantages

  • Writing a unit configuration file is slightly more involved than typing a single command.
  • Each container needs its own service file.

Deployment Guide: Turning a Container into a Systemd Service

Let’s say I have a simple web application running with Nginx. Instead of using docker run directly, I’ll create a service unit for it.

Step 1: Create the Unit File

Create a file with a .service extension in /etc/systemd/system/. I’ll name mine webapp.service.

sudo nano /etc/systemd/system/webapp.service

Step 2: Write the Configuration

This is the configuration I actually use for several personal VPS projects. Paste it into the file:

[Unit]
Description=My Web Application Container
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
# Remove old container if it exists before starting a new one
ExecStartPre=-/usr/bin/docker stop %p
ExecStartPre=-/usr/bin/docker rm %p
ExecStartPre=/usr/bin/docker pull nginx:latest

# Main startup command
ExecStart=/usr/bin/docker run --name %p \
    -p 8080:80 \
    nginx:latest

# Stop the container
ExecStop=/usr/bin/docker stop %p

[Install]
WantedBy=multi-user.target

Key parameters explained:

  • After=docker.service: Ensures the Docker daemon is running before this service starts.
  • ExecStartPre=-/usr/bin/docker stop %p: The leading - means that if this command fails (e.g., the container doesn’t exist yet), systemd will continue to the next command rather than aborting.
  • %p: A systemd specifier that expands to the service name without the extension (here: webapp). Used as the container name for easy identification.
  • Restart=always: Automatically restarts the container if it crashes.

Step 3: Enable the Service

After saving the file, you need to tell systemd about the change and activate the service:

# Reload so the system recognizes the new file
sudo systemctl daemon-reload

# Enable the service to start with the OS
sudo systemctl enable webapp.service

# Start the service immediately
sudo systemctl start webapp.service

Step 4: Check the Status

Verify the container is running:

sudo systemctl status webapp.service

A green active (running) line means success. If you see failed, run journalctl -u webapp.service -n 50 to see the specific error.

Practical Tips: Log Management and Debugging

Once you switch to systemd, you no longer need docker logs. All container output flows into journald. The biggest win when debugging is having application logs and system logs on the same timeline — no more guessing the order of events.

Watch logs in real time:

journalctl -u webapp.service -f

When you need to deploy updated code or a new image, just run:

sudo systemctl restart webapp.service

Systemd automatically walks through the ExecStartPre steps — pulling the new image, removing the old container, and restarting. No need to remember the manual sequence.

When Should You NOT Use This Approach?

Systemd solves a specific problem well — but not every problem needs this solution.

  1. Development environments: Stick with Docker Compose for speed. Editing a service file and reloading the daemon every time you change a port is a waste of time.
  2. Complex microservices architectures: If you have dozens of interconnected containers, managing dozens of service files by hand will be a nightmare. Docker Compose or Kubernetes is the right call there.

This approach works best for single-instance services on a VPS — a Telegram Bot, a personal blog, or Nginx/Traefik as a reverse proxy. If you only have two or three independent containers, this is simpler and more stable than Docker Compose.

I’m running this exact setup for an Nginx reverse proxy and a Telegram Bot on my personal VPS — and for the past several months, I haven’t had to touch them manually once. If you’re dealing with containers that refuse to start after a reboot, give this a try.

Share: