Skip to content

Commit

Permalink
use systemd Upholds with depends_on
Browse files Browse the repository at this point in the history
By default, systemd does not start a service if its dependencies started
succesfully after failing. Upholds (reverse: UpheldBy) will handle
starting dependent containers for us to mimic Compose behavior.
  • Loading branch information
aksiksi committed Dec 19, 2023
1 parent 9c5936f commit a1eb500
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 35 deletions.
49 changes: 25 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,30 +184,31 @@ If a feature is missing, please feel free to [create an issue](https://github.co
#### [`services`](https://docs.docker.com/compose/compose-file/05-services/)
| | |
|---|:---:|
| [`image`](https://docs.docker.com/compose/compose-file/05-services/#image) | ✅ |
| [`container_name`](https://docs.docker.com/compose/compose-file/05-services/#container_name) | ✅ |
| [`environment`](https://docs.docker.com/compose/compose-file/05-services/#environment) | ✅ |
| [`volumes`](https://docs.docker.com/compose/compose-file/05-services/#volumes) | ✅ |
| [`labels`](https://docs.docker.com/compose/compose-file/05-services/#labels) | ✅ |
| [`ports`](https://docs.docker.com/compose/compose-file/05-services/#ports) | ✅ |
| [`dns`](https://docs.docker.com/compose/compose-file/05-services/#dns) | ✅ |
| [`cap_add/cap_drop`](https://docs.docker.com/compose/compose-file/05-services/#cap_add) | ✅ |
| [`logging`](https://docs.docker.com/compose/compose-file/05-services/#logging) | ✅ |
| [`restart`](https://docs.docker.com/compose/compose-file/05-services/#restart) | ✅ |
| [`deploy.restart_policy`](https://docs.docker.com/compose/compose-file/deploy/#restart_policy) | ✅ |
| [`devices`](https://docs.docker.com/compose/compose-file/05-services/#devices) | ✅ |
| [`networks.aliases`](https://docs.docker.com/compose/compose-file/05-services/#aliases) | ✅ |
| [`network_mode`](https://docs.docker.com/compose/compose-file/05-services/#network_mode) | ✅ |
| [`privileged`](https://docs.docker.com/compose/compose-file/05-services/#privileged) | ✅ |
| [`extra_hosts`](https://docs.docker.com/compose/compose-file/05-services/#extra_hosts) | ✅ |
| [`sysctls`](https://docs.docker.com/compose/compose-file/05-services/#sysctls) | ✅ |
| [`shm_size`](https://docs.docker.com/compose/compose-file/05-services/#shm_size) | ✅ |
| [`runtime`](https://docs.docker.com/compose/compose-file/05-services/#runtime) | ✅ |
| [`security_opt`](https://docs.docker.com/compose/compose-file/05-services/#security_opt) | ✅ |
| [`command`](https://docs.docker.com/compose/compose-file/05-services/#command) | ✅ |
| [`healthcheck`](https://docs.docker.com/compose/compose-file/05-services/#healthcheck) | ✅ |
| | | Notes |
|---|:---:|-------|
| [`image`](https://docs.docker.com/compose/compose-file/05-services/#image) | ✅ | |
| [`container_name`](https://docs.docker.com/compose/compose-file/05-services/#container_name) | ✅ | |
| [`environment`](https://docs.docker.com/compose/compose-file/05-services/#environment) | ✅ | |
| [`volumes`](https://docs.docker.com/compose/compose-file/05-services/#volumes) | ✅ | |
| [`labels`](https://docs.docker.com/compose/compose-file/05-services/#labels) | ✅ | |
| [`ports`](https://docs.docker.com/compose/compose-file/05-services/#ports) | ✅ | |
| [`dns`](https://docs.docker.com/compose/compose-file/05-services/#dns) | ✅ | |
| [`cap_add/cap_drop`](https://docs.docker.com/compose/compose-file/05-services/#cap_add) | ✅ | |
| [`logging`](https://docs.docker.com/compose/compose-file/05-services/#logging) | ✅ | |
| [`depends_on`](https://docs.docker.com/compose/compose-file/05-services/#depends_on) | ⚠️ | Only short syntax is supported. |
| [`restart`](https://docs.docker.com/compose/compose-file/05-services/#restart) | ✅ | |
| [`deploy.restart_policy`](https://docs.docker.com/compose/compose-file/deploy/#restart_policy) | ✅ | |
| [`devices`](https://docs.docker.com/compose/compose-file/05-services/#devices) | ✅ | |
| [`networks.aliases`](https://docs.docker.com/compose/compose-file/05-services/#aliases) | ✅ | |
| [`network_mode`](https://docs.docker.com/compose/compose-file/05-services/#network_mode) | ✅ | |
| [`privileged`](https://docs.docker.com/compose/compose-file/05-services/#privileged) | ✅ | |
| [`extra_hosts`](https://docs.docker.com/compose/compose-file/05-services/#extra_hosts) | ✅ | |
| [`sysctls`](https://docs.docker.com/compose/compose-file/05-services/#sysctls) | ✅ | |
| [`shm_size`](https://docs.docker.com/compose/compose-file/05-services/#shm_size) | ✅ | |
| [`runtime`](https://docs.docker.com/compose/compose-file/05-services/#runtime) | ✅ | |
| [`security_opt`](https://docs.docker.com/compose/compose-file/05-services/#security_opt) | ✅ | |
| [`command`](https://docs.docker.com/compose/compose-file/05-services/#command) | ✅ | |
| [`healthcheck`](https://docs.docker.com/compose/compose-file/05-services/#healthcheck) | ✅ | |
#### [`networks`](https://docs.docker.com/compose/compose-file/06-networks/)
Expand Down
38 changes: 27 additions & 11 deletions compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) (*NixContaine
}

// Figure out explicit dependencies for this container.
//
// TODO(aksiksi): Support the long syntax.
// https://docs.docker.com/compose/compose-file/05-services/#long-syntax-1
for _, s := range service.GetDependencies() {
targetContainerName, ok := g.serviceToContainerName[s]
if !ok {
Expand Down Expand Up @@ -429,17 +432,6 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) (*NixContaine
c.ExtraOptions = append(c.ExtraOptions, mapToRepeatedKeyValFlag("--log-opt", logging.Options)...)
}

// Add systemd dependencies on network(s).
for _, networkName := range c.Networks {
c.SystemdConfig.Unit.After = append(c.SystemdConfig.Unit.After, g.networkNameToService(networkName))
c.SystemdConfig.Unit.Requires = append(c.SystemdConfig.Unit.Requires, g.networkNameToService(networkName))
}
// Add systemd dependency on root target.
if !g.NoCreateRootTarget {
c.SystemdConfig.Unit.PartOf = append(c.SystemdConfig.Unit.PartOf, fmt.Sprintf("%s.target", rootTarget(g.Runtime, g.Project)))
c.SystemdConfig.Unit.WantedBy = append(c.SystemdConfig.Unit.WantedBy, fmt.Sprintf("%s.target", rootTarget(g.Runtime, g.Project)))
}

// Health check.
// https://docs.docker.com/compose/compose-file/05-services/#healthcheck
if healthCheck := service.HealthCheck; healthCheck != nil {
Expand Down Expand Up @@ -489,6 +481,30 @@ func (g *Generator) buildNixContainer(service types.ServiceConfig) (*NixContaine
slices.Sort(c.ExtraOptions)
slices.Sort(c.Networks)

// Add systemd dependencies on network(s).
for _, networkName := range c.Networks {
c.SystemdConfig.Unit.After = append(c.SystemdConfig.Unit.After, g.networkNameToService(networkName))
c.SystemdConfig.Unit.Requires = append(c.SystemdConfig.Unit.Requires, g.networkNameToService(networkName))
}
// Add systemd dependency on root target.
if !g.NoCreateRootTarget {
c.SystemdConfig.Unit.PartOf = append(c.SystemdConfig.Unit.PartOf, fmt.Sprintf("%s.target", rootTarget(g.Runtime, g.Project)))
c.SystemdConfig.Unit.WantedBy = append(c.SystemdConfig.Unit.WantedBy, fmt.Sprintf("%s.target", rootTarget(g.Runtime, g.Project)))
}

// Set UpheldBy for this service's dependencies. This ensures that, when the dependency comes up,
// this container will also be started - and continuously restarted with backoff - until it comes up.
// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Upholds=
//
// Why do we need to do this? Because, by default, systemd does not attempt to start failed dependent units
// when the parent (dependency) comes up. See: https://github.com/systemd/systemd/issues/1312.
//
// Note 2: Upholds is supported in version 249+. NixOS 23.05 uses 253.6, so this should always be supported.
// See: https://github.com/NixOS/nixpkgs/blob/nixos-23.05/pkgs/os-specific/linux/systemd/default.nix#L148.
for _, containerName := range c.DependsOn {
c.SystemdConfig.Unit.UpheldBy = append(c.SystemdConfig.Unit.UpheldBy, fmt.Sprintf("%s-%s.service", g.Runtime, containerName))
}

return c, nil
}

Expand Down
6 changes: 6 additions & 0 deletions nixos-test/docker-compose.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
"/var/volumes/radarr:/config:rw"
"storage:/storage:rw"
];
dependsOn = [
"myproject-sabnzbd"
];
log-driver = "journald";
extraOptions = [
"--network-alias=radarr"
Expand All @@ -78,6 +81,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down
2 changes: 2 additions & 0 deletions nixos-test/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ services:
volumes:
- /var/volumes/radarr:/config
- storage:/storage
depends_on:
- sabnzbd
restart: unless-stopped

networks:
Expand Down
6 changes: 6 additions & 0 deletions nixos-test/podman-compose.nix
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"/mnt/media:/storage:rw"
"/var/volumes/radarr:/config:rw"
];
dependsOn = [
"myproject-sabnzbd"
];
log-driver = "journald";
extraOptions = [
"--network-alias=radarr"
Expand All @@ -79,6 +82,9 @@
partOf = [
"podman-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"podman-myproject-sabnzbd.service"
];
wantedBy = [
"podman-compose-myproject-root.target"
];
Expand Down
3 changes: 3 additions & 0 deletions systemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type UnitConfig struct {
After []string
Requires []string
PartOf []string
UpheldBy []string
WantedBy []string
// Map for generic options.
Options map[string]any
Expand All @@ -86,6 +87,8 @@ func (u *UnitConfig) Set(key string, value any) {
u.Requires = append(u.Requires, value.(string))
case "PartOf":
u.PartOf = append(u.PartOf, value.(string))
case "UpheldBy":
u.UpheldBy = append(u.UpheldBy, value.(string))
case "WantedBy":
u.WantedBy = append(u.WantedBy, value.(string))
default:
Expand Down
7 changes: 7 additions & 0 deletions templates/container.nix.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ systemd.services."{{.Runtime}}-{{.Name}}" = {
{{- end}}
];
{{- end}}
{{- if .SystemdConfig.Unit.UpheldBy}}
unitConfig.UpheldBy = [
{{- range .SystemdConfig.Unit.UpheldBy}}
"{{.}}"
{{- end}}
];
{{- end}}
{{- if .SystemdConfig.Unit.WantedBy}}
wantedBy = [
{{- range .SystemdConfig.Unit.WantedBy}}
Expand Down
9 changes: 9 additions & 0 deletions testdata/TestDocker_EnvFilesOnly_out.nix
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -211,6 +214,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -260,6 +266,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down
9 changes: 9 additions & 0 deletions testdata/TestDocker_RemoveVolumes_out.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -236,6 +239,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -286,6 +292,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down
9 changes: 9 additions & 0 deletions testdata/TestDocker_SystemdMount_out.nix
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -244,6 +247,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -294,6 +300,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down
9 changes: 9 additions & 0 deletions testdata/TestDocker_WithProject_out.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -236,6 +239,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -286,6 +292,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down
9 changes: 9 additions & 0 deletions testdata/TestDocker_out.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -232,6 +235,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-myproject-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down Expand Up @@ -281,6 +287,9 @@
partOf = [
"docker-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"docker-sabnzbd.service"
];
wantedBy = [
"docker-compose-myproject-root.target"
];
Expand Down
9 changes: 9 additions & 0 deletions testdata/TestPodman_WithProject_out.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
partOf = [
"podman-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"podman-myproject-sabnzbd.service"
];
wantedBy = [
"podman-compose-myproject-root.target"
];
Expand Down Expand Up @@ -225,6 +228,9 @@
partOf = [
"podman-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"podman-myproject-sabnzbd.service"
];
wantedBy = [
"podman-compose-myproject-root.target"
];
Expand Down Expand Up @@ -275,6 +281,9 @@
partOf = [
"podman-compose-myproject-root.target"
];
unitConfig.UpheldBy = [
"podman-sabnzbd.service"
];
wantedBy = [
"podman-compose-myproject-root.target"
];
Expand Down
Loading

0 comments on commit a1eb500

Please sign in to comment.