Skip to content

Run automations in your own environment

Who this is for: admins who want a workflow to run inside their own network, for example because it reaches a system that is not on the internet or handles data that must not leave the company.

Before you start: check these three things.

  • You need the Manage runners permission. Only admins have it by default. See Manage users and roles.
  • Your plan must include runners. The trial plan doesn’t. See Limits per plan.
  • A machine in your network that can run a Docker container and make outgoing HTTPS connections to Liitora. No incoming port is needed.

A runner is a small program you run yourself. It asks Liitora for work, runs the workflow inside your network and reports back. Runners belong to a runner group. A workflow picks a group with runs-on: self-hosted/<group>, and only runners of that group ever run it. Its runs never move to Liitora’s own workers, not even when no runner is online.

Workflows without runs-on keep running on Liitora, as before.

  1. In Settings, select Runners.
  2. Select Add a runner group.
  3. Enter a Group name: 1 to 63 lowercase letters, digits and hyphens, for example on-prem.
  4. Choose What the runners send back:
    • Only statuses and timings (metadata, the default): step data, results, error messages and logs stay in your network. Liitora sees whether each step worked and how long it took.
    • Everything, like a run on Liitora (full): step data, results and logs come back with secret values masked, so you can read them in the run view.
  5. Select Add group.
  1. In the group, select Add a runner, then Create a token.

  2. Copy the registration token. You can’t see it again. It works once, within one hour. A week after a token was used or ran out, Liitora deletes it. The audit log still shows who created it and which runner used it.

  3. Copy the Version key shown on the Runners page. It starts with ed25519:. The runner only runs workflow versions that Liitora signed with this key after a person approved them.

  4. On your machine, write the settings into a file that only the runner’s user can read, for example runner.env:

    LIITORA_RUNNER_URL=https://app.liitora.com/
    LIITORA_RUNNER_STATE_DIR=/var/lib/liitora-runner
    LIITORA_RUNNER_REGISTRATION_TOKEN=<the token>
    LIITORA_RUNNER_VERSION_KEYS=<the version key>
    LIITORA_RUNNER_NAME=dc1
    LIITORA_RUNNER_EGRESS_ALLOW=erp.example.internal:443
  5. Start the runner with the runner command of the Liitora image, with a volume for its state:

    Terminal window
    docker run -d --name liitora-runner --restart unless-stopped \
    --read-only --cap-drop ALL --security-opt no-new-privileges:true \
    -v liitora-runner-state:/var/lib/liitora-runner \
    --env-file runner.env <liitora image> runner
  6. Wait a few seconds and refresh the Runners page. The runner shows as Online.

On its first start the runner trades the token for its own credential and keeps it in the state directory. It replaces the credential by itself before it runs out. You can remove the token line from the file afterwards.

Add runs-on at the top level of the workflow:

name: Sync overdue devices from the ERP
description: Reads overdue devices from the ERP in our own network.
runs-on: self-hosted/on-prem
on:
schedule:
- cron: "0 8 * * 1-5"
steps:
- id: fetch
name: Fetch overdue devices
uses: platform/http.request@v1
with:
url: https://erp.example.internal/api/devices?overdue=true

The group must exist when you save, approve and activate the workflow, otherwise you get runner_group_missing. A person approves the version as usual, and only then can runners run it.

The approval also records what the group sends back. If someone later removes the group and adds it again so that it sends back everything instead of only statuses and timings, runs of that version fail with return_mode_not_approved and nothing is sent to the runner. Save the workflow again and approve the new version to allow it. Sending back less than approved is always fine.

Open the workflow. Where your data goes on the Overview tab says, for the version you look at:

  • Runs on: the runner group.
  • Webhook payloads enter Liitora: Yes when the workflow has a webhook trigger, because Liitora receives the delivery and hands it to the runner. It says No, received by the runner when the trigger has receive: runner. See Receive webhooks inside your network.
  • Secrets held by: usually The runner. It says Both when Liitora also holds something for this workflow: the secret of its webhook endpoint (unless the runner receives the webhook), or the credentials of the connection its poll trigger checks.
  • Logs and results returned: what the group sends back. It says Needs a new approval when the group now sends back more than the version was approved with.

The approver sees the same card before approving, and the version comparison lists every answer that changed. See Where your data goes.

The Where your data goes card on the workflow’s Overview tab for a workflow on a runner group

Secrets for a runner workflow come from your own environment, never from Liitora. Activating such a workflow doesn’t check Liitora’s secret store. The runner only reads secrets; a step that would save one fails.

From environment variables (LIITORA_RUNNER_SECRETS=env, the default):

  • LIITORA_SECRET_ERP_KEY holds the value of the secret erp_key.
  • LIITORA_SECRET_ERP_KEY__USERNAME holds the field username of that secret.

From Vault or OpenBao (LIITORA_RUNNER_SECRETS=vault): set LIITORA_RUNNER_VAULT_ADDR, the KV version 2 mount in LIITORA_RUNNER_VAULT_MOUNT (default secret) and the path below it in LIITORA_RUNNER_VAULT_PATH. Sign in with LIITORA_RUNNER_VAULT_TOKEN, or with AppRole through LIITORA_RUNNER_VAULT_ROLE_ID and LIITORA_RUNNER_VAULT_SECRET_ID. Give the runner a policy that can only read that path.

A workflow on your runner can use an AWS connection. The runner signs in to AWS itself, so the access key stays in your environment and Liitora never sees it.

  1. Give the runner both halves of the access key, under the secret name the connection uses, for example aws_key:
    • LIITORA_SECRET_AWS_KEY__ACCESS_KEY_ID holds the access key ID.
    • LIITORA_SECRET_AWS_KEY__SECRET_ACCESS_KEY holds the secret access key. With Vault or OpenBao, store them as the fields access_key_id and secret_access_key of the secret aws_key.
  2. Add the connection’s hosts to LIITORA_RUNNER_EGRESS_ALLOW: the STS endpoint of your Region and each service, for example sts.eu-north-1.amazonaws.com:443,iam.amazonaws.com:443. If the runner has a policy, list them under destinations too, and allow the secret name.

Each run gets its own AWS session, as on Liitora. The connection still has to exist in Liitora, with the Region, the services, the role and the external ID. Rotate the key in your own environment, with the steps in Rotate the access key.

LIITORA_RUNNER_EGRESS_ALLOW lists the only places your automations may call from the runner, as host:port separated by commas. Calls use HTTPS unless an entry starts with http://. Anything else is refused. Addresses on the machine itself are allowed only when you list their IP address, and link-local addresses (such as cloud metadata addresses) are always refused.

With a policy file, the runner connects only to places that are both in this list and in the policy’s destinations.

If your systems use certificates from your own certificate authority, put its PEM file in LIITORA_RUNNER_EGRESS_CA_FILE. For a private certificate on the Liitora address, use LIITORA_RUNNER_CA_FILE.

Liitora only sends the runner versions that a person approved. If you also want a rule of your own that Liitora can’t change, give the runner a policy file. It lists the destinations, secret names and actions a workflow may use, and whether workflows may contain code of their own. The runner refuses any version that goes beyond it, and the run fails with policy_refused. See Limit what your runner may run.

Setting What it does
LIITORA_RUNNER_URL The Liitora address (required)
LIITORA_RUNNER_STATE_DIR Folder where the runner keeps its credential (required)
LIITORA_RUNNER_REGISTRATION_TOKEN The token for the first start
LIITORA_RUNNER_VERSION_KEYS The version key; several separated by commas while the key changes (required)
LIITORA_RUNNER_NAME The name shown on the Runners page (default: the machine’s name)
LIITORA_RUNNER_CONCURRENCY Runs at the same time, 1 to 64 (default 2)
LIITORA_RUNNER_POLL_SECS Seconds between requests for work when idle, 1 to 300 (default 5)
LIITORA_RUNNER_CA_FILE Your certificate authority for the Liitora address
LIITORA_RUNNER_EGRESS_ALLOW Where automations may connect
LIITORA_RUNNER_EGRESS_CA_FILE Your certificate authority for those places
LIITORA_RUNNER_SECRETS env or vault
LIITORA_RUNNER_POLICY_FILE A policy file that limits which workflow versions the runner runs. See Limit what your runner may run
LIITORA_RUNNER_WEBHOOK_LISTEN Turns on receiving webhooks on the runner. See Receive webhooks inside your network for this and the other LIITORA_RUNNER_WEBHOOK_ settings

Runs of a runner workflow wait in the queue until a runner of its group asks for work. If they wait more than 10 minutes and no runner of the group contacted Liitora, admins get the runner_unavailable alert, once per outage.

Check that:

  • the runner container is running, and its log doesn’t say to register again;
  • the machine can reach the Liitora address over HTTPS;
  • the group on the Runners page still has the runner. If you removed the runner or its group, create a new token and start the runner with it.

If a runner stops in the middle of a run, the run goes back to the queue, at the latest about two minutes later, and another runner of the group picks it up.

A step that uses one of these fails with runner_unsupported:

  • calling another workflow;
  • saving files from a run;
  • setting items aside;
  • certificate sign-in;
  • retrying a run from a chosen step.

Change detection that compares with the last successful run (platform/sync.diff with snapshot: true) works on a runner when its group sends everything back (full). The snapshot keeps the record keys and a fingerprint of each record on Liitora, never the field values. It is saved when the run succeeds, as on Liitora.

In a group that sends back only statuses and timings, such a step fails with runner_unsupported, because the keys would reach Liitora. Compare with a target list instead, which keeps everything on the runner.

A runner sends at most about 2 MB of record keys and fingerprints for one step, roughly 30,000 records with short keys. For more, split the records over several steps, or run the workflow on Liitora.