4

Summary

If I have a systemd template unit foo@.service, how can I symlink to or create an alias for a specific template instance, such that, for example, bar.service would be a shortcut for foo@bar.service?

Background

I have a couple of services run by docker-compose.

/var/docker-compose/nginx/docker-compose.yml
/var/docker-compose/nextcloud/docker-compose.yml
/var/docker-compose/diaspora/docker-compose.yml

To manage these services with systemd, I've adopted an approach to use template units in order to avoid duplication as all the docker-compose unit files would almost be the same.

# /etc/systemd/system/docker-compose@.service

[Unit]
Description=%i service with docker compose
Requires=docker.service
After=docker.service

[Service]
Restart=always

WorkingDirectory=/var/docker-compose/%i

# Remove old containers, images and volumes
ExecStartPre=/usr/bin/docker-compose down -v
ExecStartPre=/usr/bin/docker-compose rm -fv
ExecStartPre=-/bin/bash -c 'docker volume ls -qf "name=%i_" | xargs docker volume rm'
ExecStartPre=-/bin/bash -c 'docker network ls -qf "name=%i_" | xargs docker network rm'
ExecStartPre=-/bin/bash -c 'docker ps -aqf "name=%i_*" | xargs docker rm'

# Compose up
ExecStart=/usr/bin/docker-compose up %i

# Compose down, remove containers and volumes
ExecStop=/usr/bin/docker-compose down -v

[Install]
WantedBy=multi-user.target

Goal

Now I would like to have aliases or symlinks to the individual services to make starting and stopping them a little shorter.

Rather than

systemctl stop docker-compose@nginx

I would like to type:

systemctl stop nginx

What does not work

  • I've tried to create a symlink:

    cd /etc/systemd/system
    ln -s docker-compose@nginx.service nginx.service
    

    but nginx.service is not found when I want to start or stop it.

  • I've tried to define an alias in the template unit:

    [Install]
    Alias=%i.service
    

    but, if I understand this correctly, this is not allowed and it did not work for me.

  • I haven't found a way to "import" or "require" a base unit file and overwrite just the WorkingDirectory line. Then, I could create separate, short files like this:

    # /etc/systemd/system/nginx.service
    
    Import=docker-compose@.service
    
    WorkingDirectory=/var/docker-compose/nginx
    

Thanks!

4
  • I've posted my workaround, but I'm still looking for a systemd solution.
    – fiedl
    Jan 21, 2021 at 12:23
  • Did you eventually find a proper solution?
    – Sam Sirry
    Apr 29, 2021 at 18:09
  • 1
    @SamSirry: No, sorry. I'm using my posted workaround for now.
    – fiedl
    Apr 29, 2021 at 18:26
  • 1
    After adding that Alias line, you need to update systemd via systemctl daemon-reload and then you need re-enable the service.
    – AnthonyK
    Feb 3 at 9:17

3 Answers 3

1

I tried to make use of the properties considered for unit names containing dashes (-) according to the archlinux man pages, and it worked:

Moreover for unit names containing dashes (-), the set of directories generated by repeatedly truncating the unit name after all dashes is searched too. Specifically, for a unit name foo-bar-baz.service not only the regular drop-in directory foo-bar-baz.service.d/ is searched but also both foo-bar-.service.d/ and foo-.service.d/.

and as another note from the man page:

Along with a unit file foo.service, a "drop-in" directory foo.service.d/ may exist. All files with the suffix .conf from this directory will be merged in the alphanumeric order and parsed after the main unit file itself has been parsed.

In my working example, this is the final structure of the files at the root of my unit search path (i.e., /etc/systemd/system/):

│    docker-a.service
│    docker-b.service  
│    docker-c.service
│
└─── docker-.service.d
    │    docker.conf
    │ 
             

Also, docker.conf and docker-a.service files are:

# /etc/systemd/system/docker-.service.d/docker.conf

[Unit]
Description=We named our service [%j] service
After=network.target

[Service]
WorkingDirectory=/home/arman/%j
ExecStartPre=/bin/bash -c 'echo "The prestart command for %j was done!" >> log.txt'
ExecStart=/bin/bash -c 'echo "The start command for %j was done!" >> log.txt'

# /etc/systemd/system/docker-a.service

[Install]
WantedBy=multi-user.target
Alias=a.service

I did it with the following steps:

  1. Firstly, create a drop-in directory like docker-.service.d at the root of a unit search path directory (e.g. /etc/systemd/system).
  2. Then, create a .conf file like docker.conf at the created drop-in directory and write your [Unit] and [Service] sections in that file using the %j specifier:

%j: This is the string between the last - and the end of the prefix name.

  1. After that, create one service file for each of your intended services next to the drop-in directory in the root search path. They should just consist of the [Install] section to explicitly determine their alias name with the Alias option. (I also tried to put this section in the docker.conf file too, but the [Install] section could not be found when the services started.) Be careful that the names of these services should satisfy the constraints.
  2. Now, If you run systemctl enable docker-{alias}.service for each created service, you'll be able to control the service just by its {alias}. That's it!
2
  • P.S. Step 3 clarification: For example, in my case, the names obey the docker-{alias}.service format that {alias} is the alias service determined in the service file as the Alias option without the type suffix. (e.g. "nginx") Apr 4 at 2:53
  • P.S. Step 4 clarification: It means if you've entered Alias=nginx.service in the [Install] section of service, by enabling the service, you can start it by running systemctl start nginx. That's it! Apr 4 at 2:54
0

How about accepting that command as it is and just use bash aliased for your commandline:

File ~/.bash_aliases

alias nginx-stop="systemctl stop docker-compose@nginx"
alias nginx-start="systemctl start docker-compose@nginx"
alias nextcloud-stop="systemctl stop docker-compose@nextcloud"
alias nextcloud-start="systemctl start docker-compose@nextcloud"
alias diaspora-stop="systemctl stop docker-compose@diaspora"
alias diaspora-start="systemctl start docker-compose@diaspora"
1
  • 1
    Thanks, I thought about that but didn't really like the idea: I think, my fingers just want to type systemctl restart nginx because of the many other machines I had nginx running without docker previously. I'm regarding bash aliases (and maybe accepting duplication and just copy/pasting the unit files) fallback solutions if I can't find a systemd-way of achieving this.
    – fiedl
    Jan 12, 2021 at 1:01
0

The following workaround is not systemd-native but creates systemd service files using a Rakefile.

Workaround

This assumes that ruby is installed on the host system.

  1. Check that /etc/systemd/system/docker-compose@.service is present. Within this file %i is used as variable for the service name, e.g. "nginx".

  2. Create a Rakefile in /var/docker-compose/:

    # /var/docker-compose/Rakefile
    
    TEMPLATE_SERVICE_FILE = "/etc/systemd/system/docker-compose@.service"
    
    task :systemd_units do
      print "Creating systemd units:\n"
    
      Dir.glob("/var/docker-compose/*/docker-compose.yml").each do |compose_file|
        service_name = compose_file.split("/")[-2]
        unit_file = "/etc/systemd/system/#{service_name}.service"
        unit_file_content = File.read TEMPLATE_SERVICE_FILE
        unit_file_content.gsub! "%i", service_name
        File.write unit_file, unit_file_content
        print "#{unit_file}\n"
      end
    end
    
  3. Run rake from the /var/docker-compose/ directory.

This will explicitly create a separate unit file for each service, e.g. /etc/systemd/system/nginx.service based on the template file /etc/systemd/system/docker-compose@.service.

Pros:

  • systemctl start nginx works
  • systemctl enable nginx works
  • Tab completion works

Cons:

  • One has to re-run rake when changing the template file or adding/removing services from the docker-compose directory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.