OliveTin

Web buttons that safely run predefined shell commands

Automation & Workflows ★ 3.8k stars Easy setup AGPL-3.0

OliveTin gives users a simple web interface of buttons that trigger predefined shell commands, making safe, restricted access to scripts easy. It is ideal for letting non-technical users run approved actions.

Key features

  • Web buttons for commands
  • Restricted predefined actions
  • Argument validation
  • Lightweight binary

Pros & cons

Strengths

  • Safe access to scripts
  • Easy to configure

Trade-offs

  • Limited to predefined actions
  • No complex workflows

OliveTin replaces

Last reviewed Sep 13, 2026 · 855 words

The best use of OliveTin I have seen is a page with three buttons: "Restart Jellyfin", "Wake the office PC" and "Run backup now", on a Pi in a hallway, pressed by people who do not know what SSH is. That is the whole product. You write a YAML file listing commands, OliveTin renders them as buttons with icons, and anyone with access to the page can run those commands and nothing else. It is a single Go binary, uses about 64 MB of RAM, and takes 10 minutes to set up. The catch is in the scoping: it is a button board, not a job scheduler, and the moment you want dependencies between steps or approvals, you have outgrown it.

The config file is the product

listenAddressSingleHTTPFrontend: 0.0.0.0:1337

actions:
  - title: Restart Jellyfin
    icon: restart
    shell: docker restart jellyfin
    timeout: 30

  - title: Ping a host
    icon: ping
    shell: ping -c {{ count }} {{ host }}
    arguments:
      - name: host
        type: ascii_identifier
      - name: count
        type: int

Every button is one entry. shell is the command, timeout kills it, and arguments let the user fill in values that OliveTin validates against a type before substitution. That validation is what makes it safe to expose to non-technical people: ascii_identifier refuses spaces and shell metacharacters, int refuses everything but digits, and a regex type lets you define your own allowed shape. Never use an unrestricted argument type for something that reaches a shell; the whole point is that the user cannot construct a command you did not write.

The web UI listens on port 1337 by default and reloads the config when the file changes, so iteration is edit, save, refresh.

Run it where the commands need to run

OliveTin executes commands on the host it runs on, as the user it runs as. In Docker that means the container needs whatever the commands need: mount the Docker socket to restart containers, mount an SSH key to reach other machines, install tools into a derived image if a script depends on them.

services:
  olivetin:
    image: jamesread/olivetin:latest
    ports:
      - "1337:1337"
    volumes:
      - ./config.yaml:/config/config.yaml:ro
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped

The bare binary on the host, installed from the release packages, is often simpler for a machine where the buttons mostly touch that machine's own services. Either way, remember that anyone who can press the buttons can do whatever those commands do, with the privileges you gave the process; mounting the Docker socket is effectively root on the host, so the next section is not optional.

Put authentication in front of it

OliveTin has grown its own login options over time, including local users and OAuth providers, but the pattern I trust is the same as for any small admin tool: a reverse proxy that requires authentication and forwards a username header, with OliveTin's access control rules mapping that username to the actions it may see. Then a per-user view is possible: the household sees the three friendly buttons, and the admin sees the twenty scary ones. Keep it off the public internet, or behind Tailscale, even with authentication; a page whose purpose is running shell commands is not something to leave one credential away from the world.

Beyond buttons: entities, triggers and the API

Two features stretch the "just buttons" description. Entities let OliveTin read a file (a JSON list of containers, say) and generate one button per entry, so "Restart X" exists for every service without twenty config stanzas. And every action can be started over HTTP, so a monitoring tool or a webhook can push the button. Healthchecks can be told to fire an OliveTin action when a cron job goes silent; Uptime Kuma can restart a service when a check fails. That takes it from hallway convenience to a small self-healing layer, with the command logic still in one readable file.

Where it stops and what takes over

No scheduling beyond what the host's cron already does. No multi-step workflows, no conditionals, no output parsing between steps, no approvals, no audit trail beyond logs. If you need job pipelines with history and access policies, Rundeck is the heavier tool that the tagline positions OliveTin against, and its alternatives page puts both in context. If you need event-driven automation across services with branching logic, n8n is the right shape. The automation category has more, but OliveTin is deliberately the smallest thing in it, and that is why it works.

What I'd do

Install the binary on the box that runs your containers, write five buttons for the things people actually ask you to do, use typed arguments for anything that takes input, and put it behind your existing proxy authentication with per-user action visibility. Wire one action to a monitoring webhook so a failed service restarts itself. Resist adding anything that needs a second step; when that urge arrives, that job belongs in Rundeck or n8n and OliveTin stays the button board it was meant to be.

Compare OliveTin

1 head-to-head comparisons.

Similar automation & workflows apps