A logo showing the text blog.marcnuri.com
Español
Home»Cloud Native»Docker container as a Linux system service

Recent Posts

  • 2025 Year in Review: The Year of AI
  • Synology DS224+: How to upgrade hard drives in RAID 1
  • Fabric8 Kubernetes Client 7.5 is now available!
  • Boosting My Developer Productivity with AI in 2025
  • Black Box vs White Box Testing: When to Use Each Approach

Categories

  • Artificial Intelligence
  • Backend Development
  • Cloud Native
  • Engineering Insights
  • Frontend Development
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Quality Engineering
  • Tools

Archives

  • January 2026
  • December 2025
  • October 2025
  • September 2025
  • July 2025
  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • March 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • October 2017
  • August 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • November 2014
  • October 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

Docker container as a Linux system service

2017-12-28 in Cloud Native tagged DevOps / Docker / Linux / Portainer / Service / Systemctl / systemd by Marc Nuri | Last updated: 2026-01-26
Versión en Español

Introduction

There are many different ways to orchestrate Docker container management, initialization, deployment, etc. Docker provides its own Compose tool and Swarm mode. There are also third-party container orchestration tools such as Kubernetes, Rancher, and Apache Mesos.

For simple deployments on a single Linux host, you might not need any of these complex solutions. Docker daemon offers simple means to start, stop, manage, and query the status of deployed containers. In this post, we'll cover how to use a Docker + systemd approach to deploy containers as Linux services without the need for third-party tools or complex deployment descriptors.

In this tutorial, we will show how to deploy Portainer CE as a Linux systemd service.

docker as a linux systemd service
docker as a linux systemd service

Docker restart policies

Before diving into systemd integration, it's worth mentioning that Docker has built-in restart policies that can handle container restarts automatically.

The --restart flag supports several policies:

PolicyDescription
noDo not automatically restart the container (default)
on-failure[:max-retries]Restart only if the container exits with a non-zero status
alwaysAlways restart the container regardless of the exit status
unless-stoppedLike always, but doesn't restart containers that were explicitly stopped

For example, to run Portainer with automatic restarts:

bash
docker run -d --name portainer --restart unless-stopped \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts

This approach is simpler than systemd integration and works well for most use cases. However, systemd integration offers additional benefits:

  • Dependency management: Start containers after specific services (network, databases, etc.)
  • Unified logging: Container logs appear in journald alongside system logs
  • Consistent management: Use familiar systemctl commands for all services
  • Boot ordering: Fine-grained control over startup sequence

Existing container

The simplest method to deploy a container as a service is to create a Docker container with a given name and then map each of the Docker operations (start and stop) to systemd service commands.

bash
docker run -d --name portainer \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts

Once we've created this container, we can start, stop, and restart it using the regular Docker commands by indicating the container name (docker stop portainer, docker start portainer, docker restart portainer).

We can create a new systemd unit file with the service description by creating a new file in /etc/systemd/system/. For this example, we'll create a new portainer.service file with the following contents:

portainer.service
[Unit]
Description=Portainer container
After=docker.service
Wants=network-online.target docker.socket
Requires=docker.socket

[Service]
TimeoutStartSec=0
Restart=always
ExecStart=/usr/bin/docker start -a portainer
ExecStop=/usr/bin/docker stop -t 10 portainer

[Install]
WantedBy=multi-user.target

The unit file creates a new service and maps docker start and docker stop commands to the service start and stop sequences.

Key configuration explained:

  • After=docker.service: Ensures this service starts only after Docker is ready
  • Requires=docker.socket: Creates a hard dependency on the Docker socket
  • Wants=network-online.target: Waits for network to be available (soft dependency)
  • TimeoutStartSec=0: Disables startup timeout (useful if the image needs to be pulled)
  • Restart=always: Automatically restart the container if it stops

After creating the unit file, reload systemd to pick up the new service:

bash
systemctl daemon-reload

We can now start/stop the service using the corresponding commands:

bash
systemctl start portainer
systemctl stop portainer

To check the service status:

bash
systemctl status portainer

To enable the service to start automatically at boot:

bash
systemctl enable portainer.service

Create container if applicable on start

We can improve the previous unit file to create the container if it doesn't exist. This approach lets you skip the manual container creation step (docker run…) and deploy containers by simply creating the service descriptor unit file.

portainer.service
[Unit]
Description=Portainer container
After=docker.service
Wants=network-online.target docker.socket
Requires=docker.socket

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop %n
ExecStartPre=-/usr/bin/docker rm %n
ExecStartPre=/usr/bin/docker pull portainer/portainer-ce:lts
ExecStart=/usr/bin/docker run --rm --name %n \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:lts
ExecStop=/usr/bin/docker stop -t 10 %n

[Install]
WantedBy=multi-user.target

This improved unit file uses several best practices:

  • %n variable: Expands to the service name (e.g., portainer.service), making the unit file more portable
  • Dash prefix (-): The - before ExecStartPre commands means "do not fail if this command fails". This is useful because the container might not exist on first run
  • docker pull: Ensures you always run the latest version of the image
  • --rm flag: Automatically removes the container when it stops, ensuring a clean state on restart
  • Foreground mode: Unlike the previous example, this runs docker run directly (not in detached mode), allowing systemd to properly monitor the process

Alternative: Conditional container creation

If you prefer to keep an existing container rather than recreating it each time, use this approach:

portainer.service
[Unit]
Description=Portainer container
After=docker.service
Wants=network-online.target docker.socket
Requires=docker.socket

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=/bin/bash -c "/usr/bin/docker container inspect portainer 2> /dev/null || /usr/bin/docker run -d --name portainer -p 9443:9443 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:lts"
ExecStart=/usr/bin/docker start -a portainer
ExecStop=/usr/bin/docker stop -t 10 portainer

[Install]
WantedBy=multi-user.target

Systemd unit files have certain limitations when executing commands in the ExecStart, ExecStop, ExecStartPre definitions. To overcome these limitations, we encapsulate the command inside a /bin/bash -c command for more flexibility.

The first command (docker container inspect) checks if a container with this name already exists. If it throws an error (container doesn't exist), the next instruction runs to create it.

It's important to execute the docker run command with the -d (detached) flag when using this approach. Otherwise, the command won't return and the next step (ExecStart) won't run, preventing systemd from becoming active.

Depending on whether the container exists, the service start sequence behaves differently:

  • Container already exists:
    • ExecStartPre returns with no error on the first command (docker container inspect…), so the docker run… command doesn't execute
    • ExecStart runs normally and the process attaches to the container
  • Container doesn't exist:
    • ExecStartPre returns an error on the first command, triggering the second command to create and start the container in detached mode
    • ExecStart runs (docker start) which attaches to the already-running container

Viewing container logs

One benefit of running containers through systemd is unified logging. Container output is captured by journald, and you can view logs using the standard journalctl command:

bash
# View all logs for the service
journalctl -u portainer.service

# Follow logs in real-time
journalctl -u portainer.service -f

# View logs since last boot
journalctl -u portainer.service -b

Podman alternative

If you're looking for tighter systemd integration, consider Podman as an alternative to Docker. Podman is a daemonless container engine that integrates natively with systemd through a feature called Quadlet.

With Quadlet, you create simple declarative files (.container, .pod, .network) instead of writing systemd unit files manually. Place them in /etc/containers/systemd/ for root containers or ~/.config/containers/systemd/ for rootless containers.

Example Quadlet file for Portainer:

/etc/containers/systemd/portainer.container
[Container]
ContainerName=portainer
Image=docker.io/portainer/portainer-ce:lts
PublishPort=9443:9443
Volume=/run/podman/podman.sock:/var/run/docker.sock
Volume=portainer_data:/data

[Service]
Restart=always

[Install]
WantedBy=default.target

After creating this file, run systemctl daemon-reload and the container will be managed as a native systemd service.

Podman's advantages for systemd integration include:

  • Daemonless architecture: No single point of failure like dockerd
  • Rootless containers: Run containers without root privileges
  • Native systemd integration: Quadlet generates optimized unit files automatically
  • Auto-update support: Automatically pull and restart when new images are available

Conclusion

In this tutorial, I've shown several ways to deploy Docker containers as Linux systemd services:

  1. Docker restart policies: The simplest approach for basic restart handling
  2. Systemd unit files: For dependency management, unified logging, and boot ordering
  3. Podman with Quadlet: The most integrated solution for systemd-based container management

The Docker + systemd approach works well for:

  • Single-host deployments that don't need full orchestration
  • Servers where you want containers managed alongside other systemd services
  • Environments with complex service dependencies
  • Teams familiar with systemd who want consistent management across all services

For production deployments at scale, consider Kubernetes or Docker Swarm. For simpler single-host scenarios, the approaches described in this post provide a lightweight alternative that leverages your existing Linux knowledge.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Comments in "Docker container as a Linux system service"

  • Avatar for Ed McGuigan
    Ed McGuigan
    2020-06-04 12:45
    Thanks for this Marc.

    I am just setting up a simple DNS server for a home lab and I wanted to do it with CoreDNS in a docker container. This was a great help.

    Having a little trouble getting the container to stop but I will try to figure that out. I am also going to work on having a template file so I can set up multiple instances as detailed at https://coreos.com/os/docs/latest/using-environment-variables-in-systemd-units.html.

    Cheers again,

    Ed.

Post navigation
Field injection is not recommended – Spring IOCSpring Boot as a Linux service
© 2007 - 2026 Marc Nuri