How to Run your Docker Solution as a Service

Running a Docker solution as a service is an ideal way to enable automatic startup, shutdown and logging. This post describes how to create and install systemd services for Docker-based solutions on Linux.

 
Docker as a Service

Creating a systemd Service

A key step in finalizing a Docker-based solution is defining processes for automatic startup, graceful shutdown and logging.  Historically these have been performed through a series of init scripts.  However, the prevalence of systemd on modern Linux releases has eliminated that option.

This post describes creating systemd services for your solution in the simplest way possible.  It provides two example services files and explains how to modify, install and use them for your solution.

The process is broken down into these sections:

Absolute Path Requirement

Service files require the use of absolute paths for executables and scripts referenced.  For example, a startup command cannot specify docker start ..., but instead must use /usr/bin/docker start ....  This requires performing the following tasks before a service file can be created:

  • Gather the absolute path of all executables, scripts and docker-compose files.
  • Establish a directory to serve as the permanent home of the solution
    • This is necessary as references to solution files become hard-coded into the service
    • The location should not be dependant on a specific user account, to simplify provisioning
    • A common practice is to create a directory under /opt, such as /opt/example-project.

If the solution currently exists in a different directory, copy the files to the new location and review all scripts and docker-compose files for directory specific references.   If you’re using docker-compose, be aware that the Docker network-name may change if the solution uses the default network.  This is because docker-compose uses the parent directory of the compose file when naming the default network.

Example Service File for Docker-Compose 

If your solution uses docker-compose, this example file can be used by changing the highlighted lines as described below.

[Unit]
Description=Docker Example Solution
After=network-online.target docker.socket var-lib-docker-overlay2.mount

[Service]
Type=notify
WorkingDirectory=/opt/docker-example
ExecStart=/usr/local/bin/docker-compose up -d
ExecStop=/usr/local/bin/docker-compose down
ExecReload=/usr/local/bin/docker-compose up -d
RemainAfterExit=yes
Restart=on-error
RestartSec=180
StartLimitInterval=550
StartLimitBurst=3

[Install]
WantedBy=multi-user.target

Changes to Make:

After= sets any services and other system units that must be started before this service can start

  • If the solution maps volumes from the host to containers, add var-lib-docker-overlay2.mount
    • This ensures the Docker Volume mounting service is started
  • If the solution maps volumes to external storage on the host, add a the mount unit for that drive to ensure the external drive is mounted before the service starts
    • Determine where the external drive is mounted on the computer. ex: /mnt/data
    • Use this command to find the name of the “mount unit”:
    • systemctl list-units | grep '/mnt/ssd' | awk '{ print $1 }'
    • Add the mount unit to the list, for this example it’s likely mnt-data.mount

Description= sets sets the descriptive name that is displayed for the service in logs and when service status is queried.

WorkingDirectory= sets the “solution home” directory referenced in the previous section of this post.

  • This should be set to the directory containing the docker-compose.yml file
  • Specifying this parameter eliminates the need to specify the full pathname to the compose file on the Exec settings

Example Service File using Docker commands 

If your solution is launched with docker run and docker stop commands, you can modify this example as described.

[Unit]
Description=Docker Example Solution
After=network-online.target docker.socket var-lib-docker-overlay2.mount

[Service]
Type=notify
ExecStartPre=-/usr/bin/docker kill example-container
ExecStartPre=-/usr/bin/docker rm example-container
ExecStartPre=/usr/bin/docker pull myrepo/example-container
ExecStart=/usr/bin/docker run --rm --name=example-container -d -v /var/run/docker.sock:/var/run/docker.sock -it myrepo/example-container
ExecStop=/usr/bin/docker stop example-container
RemainAfterExit=yes
Restart=on-error
RestartSec=180
StartLimitInterval=550
StartLimitBurst=3

[Install]
WantedBy=multi-user.target

Unit Section:

After= sets any services and other system units that must be started before this service can start

  • See notes for docker-compose example for description

Description= sets sets the descriptive name that is displayed for the service in logs and when service status is queried.

Service Section:

While this example may look complex, it only performs two primary tasks:

  • during service startup it executes docker run to start a container
  • during service stop it executes docker stop

The ExecStartPrecommands prepare the environment before executing docker run

  • Lines 7 and 8, ensure the container can start by stopping and removing the container if it already exists
  • These commands are prefixed with  (minus sign) so the service continues starting even if these commands fail
  • Line 9, which pull (or updates) the image is optional

Change the ExecStart command in line 10 to include:

  • the container name your solution uses
  • volume mounts, environment variables, ports or other parameters
  • the name of the image used to start the container

Note: This example only starts one container.  To start multiple containers, simply add additional ExecStartPre, ExecStart ,ExecStop commands as necessary.

Installing the Service

Now that a service file has been created, it needs to be installed, enabled and started.  When using the commands below, replace MYSERVICE with the name of the service you created.

Once the service file has been created, it is installed by copying it to /lib/systemd/system/ with the command:

sudo cp MYSERVICE.service /lib/systemd/system/

After it’s copied into that directory, reload the daemon with the command:

sudo systemctl daemon-reload

Next, enable the service with the command:

sudo systemctl enable MYSERVICE

A message should be displayed that it created symlinks in /etc/systemd/system/ and /etc/systemd/system/multi-user.target.wants/

These symlinks are automatically removed when a service is disabled

The service can be started with the command:

sudo systemctl start MYSERVICE

It can be stopped by using stop in place of start.

The status and log can be retrieved with the command:

sudo systemctl status MYSERVICE

Making Service Changes

When a Docker-based solution is running as a service, it’s important that changes are made carefully, so that the service doesn’t become out of sync with a running solution.  The processes for each type of change is listed below.

Process when making changes to the service definition file:

  1. Stop the service: sudo systemctl stop MYSERVICE
  2. Disable the service: sudo systemctl disable MYSERVICE
  3. Copy the new service file: sudo cp MYSERVICE.service /lib/systemd/system/
  4. Reload the Daemon: sudo systemctl daemon-reload
  5. Enable Updated Service: sudo systemctl enable MYSERVICE
  6. Start the Updated Service: sudo systemctl start MYSERVICE

Process when making changes to docker-compose files or control scripts that change the docker service-names, container names, network names or volumes:

  1. Stop the service: sudo systemctl stop MYSERVICE
  2. Copy the changed file: sudo cp docker-compose.yml /opt/MYSERVICE/
  3. Start the Service: sudo systemctl start MYSERVICE

When making changes to docker-compose files or control scripts that don’t change the items listed above:

  • Reload configuration files: sudo systemctl reload MYSERVICE

The following commands are useful when troubleshooting errors or problems:

  • Status and log of service: sudo systemctl status MYSERVICE
  • Full Logs for service: journalctl -u MYSERVICE
  • Full Logs for service with word-wrapping: SYSTEMD_LESS=FRXMK journalctl -u MYSERVICE

SHARE THIS

LinkedIn
Facebook
Google+
Twitter

Leave a Reply

Follow Me
Recent Posts
How to Install Terraform in One Easy Step
InfoSec’s Achilles’ Heel: Physical Security
Mac Linux Post-Install Configuration Revisited
Easily Create Bootable USB Devices on any OS
@RobertPeteuil Tweets