Skip to content

Run a oneshot job

A oneshot container runs a command to completion, such as a database migration or a backup, instead of staying up. syslet writes its unit but never starts or stops it, so another container, a timer or you have to start it.

Define the job

Set desiredState: "oneshot":

Text Only
1
2
3
4
5
6
7
sysdef: containers: migrate: spec: {
    desiredState: "oneshot"
    unit: Container: {
        Image: "registry.example.com/app:1.4"
        Exec: "migrate"
    }
}
JSON
{
  "apiVersion": "v1",
  "type": "container",
  "name": "migrate",
  "desiredState": "oneshot",
  "unit": {
    "Container": {
      "Image": "registry.example.com/app:1.4",
      "Exec": "migrate"
    }
  }
}

syslet sets [Service] Type=oneshot and no [Install] section, so the job doesn't start at boot on its own. Setting Type=oneshot yourself on a "running" container is rejected.

Choose how the job starts

Before another container

Make the container that needs the job, here app, require it:

Text Only
1
2
3
4
5
6
sysdef: containers: app: spec: {
    unit: Unit: {
        Requires: "migrate.container"
        After: "migrate.container"
    }
}
JSON
1
2
3
4
5
6
"unit": {
  "Unit": {
    "Requires": "migrate.container",
    "After": "migrate.container"
  }
}

systemd now runs migrate to completion every time app starts, and doesn't start app if migrate fails. Unlike between two long-running containers (see Avoid Requires=), Requires= is safe here, since syslet never stops the job.

To skip the job on later starts of app, such as a restart by an apply, keep it active after it exits:

Text Only
1
2
3
sysdef: containers: migrate: spec: {
    unit: Service: RemainAfterExit: "yes"
}
JSON
1
2
3
"Service": {
  "RemainAfterExit": "yes"
}

The job then runs once per boot: a change to its spec doesn't run it again until the next boot, unless you restart it by hand with systemctl restart migrate, which restarts app too.

On a schedule

syslet doesn't manage timers, so write one by hand, e.g. /etc/systemd/system/backup.timer for a job named backup:

backup.timer
1
2
3
4
5
6
[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
Bash
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer

The timer starts backup.service, the service of the same name. Leave RemainAfterExit= unset on a scheduled job, since a timer can't start a job that's still active.

By hand

Start the job's service on the host:

Bash
ssh web01 sudo systemctl start migrate

systemctl start waits until the job exits, and fails if the job fails.

Preview and apply

Bash
cue cmd plan
cue cmd apply
Bash
cat hosts/web01/*.json | ssh web01 sudo syslet --diff --stdin
cat hosts/web01/*.json | ssh web01 sudo syslet --stdin

The plan shows no service action for the job itself. If another container requires it, the new [Unit] options restart that container, which runs the job.