# With Podman

> Installing · https://spoletta.com/docs/install-podman

Run Spoletta on one machine with Podman: MySQL, the webapp and the worker that runs agents and scripts, each in a container built from Spoletta's source. Podman runs them without a daemon, and without root.

## What you need

-   [Podman](https://podman.io/docs/installation) 4 or later and [podman-compose](https://github.com/containers/podman-compose): `podman compose version` prints a version. On macOS and Windows, Podman runs containers in a virtual machine; start it once with `podman machine init` and then `podman machine start`.
-   `git`, `openssl`, and access to the Spoletta repository.
-   An API key for at least one model provider, such as Anthropic or OpenAI.
-   About 4 GB of memory free for the three containers.

## Install

1.  **Get the source**
    
    ```sh
    git clone https://github.com/Nucc/spoletta.git
    cd spoletta
    ```
    
2.  **Choose the models**
    
    The worker runs agents with [pi](https://github.com/earendil-works/pi), and offers the models your pi config names: `~/.pi/agent/settings.json` and `models.json`. If you use pi already, keep yours. Otherwise make both; these offer one OpenAI model:
    
    ```sh
    mkdir -p ~/.pi/agent
    [ -f ~/.pi/agent/models.json ] || echo '{}' > ~/.pi/agent/models.json
    [ -f ~/.pi/agent/settings.json ] || cat > ~/.pi/agent/settings.json <<'EOF'
    {
      "defaultProvider": "openai",
      "defaultModel": "gpt-5.5",
      "enabledModels": ["openai/gpt-5.5"]
    }
    EOF
    ```
    
    `enabledModels` lists every model agents may pick, as `provider/model`; `models.json` adds providers pi doesn't know, such as a gateway of your own.
    
    > **Both files must exist:** Compose mounts the two files into the worker. Where one is missing, compose makes an empty directory in its place and the worker stops on starting. Should that happen, delete the directory, make the file and start again.
    >
    > To keep them elsewhere, set `PI_CONFIG_DIR` in `.env` to their directory.
    
3.  **Write the settings**
    
    Settings go in a `.env` file next to `compose.yaml`. Make one with passwords for MySQL and the secret key that seals the model keys and tokens kept in the database:
    
    ```sh
    cat > .env <<EOF
    MYSQL_ROOT_PASSWORD=$(openssl rand -hex 16)
    MYSQL_PASSWORD=$(openssl rand -hex 16)
    SECRET_KEY=$(openssl rand -base64 32)
    EOF
    chmod 600 .env
    ```
    
    MySQL takes its passwords the first time it starts, so set them before then. Copy `SECRET_KEY` to wherever you keep your backups: the webapp won't start without the key that sealed its secrets. See [Sealing secrets](https://spoletta.com/docs/sign-in.md#secrets).
    
4.  **Give the worker your model keys**
    
    Export them in the shell you start compose from, which passes them on to the worker:
    
    ```sh
    export OPENAI_API_KEY=...
    ```
    
    Not in `.env`: podman-compose reads that file only to fill in `compose.yaml`, and the worker takes its model keys from the shell. The same goes for the Sentry and Jira settings scripts read.
    
    Or skip this, and once Spoletta runs, paste them in **Settings**, **Model keys**, where they are kept sealed and survive restarts. See [Model keys](https://spoletta.com/docs/admin.md#model-keys).
    
5.  **Start it**
    
    ```sh
    podman compose up --build -d
    ```
    
    The first build takes a few minutes. `podman compose ps` shows the three containers, and `podman compose logs -f webapp worker` what they are doing.
    
6.  **Open it**
    
    Go to `http://localhost:8080`, and build [your first flow](https://spoletta.com/docs/quickstart.md).
    

> **Only on this machine until people sign in:** Without sign-in, anyone who reaches the webapp can do anything in it, running scripts on the worker included, so compose publishes it, and MySQL, on this machine's loopback only. Set up sign-in with your identity provider first ([Sign-in and secrets](https://spoletta.com/docs/sign-in.md)), then add `PUBLISH_HOST=0.0.0.0` to `.env` and run `podman compose up -d` to reach it from other machines.

## More settings

All go in `.env`, and take effect with `podman compose up -d`. The comments in `compose.yaml` list every one.

| Setting | What it is for |
| --- | --- |
| `GIT_TOKEN`, `GIT_USERNAME` | Cloning the repositories of flows that have them over https, such as a GitHub token with `contents:read`. See [Workspaces and git](https://spoletta.com/docs/workspaces.md). |
| `SENTRY_AUTH_TOKEN`, `SENTRY_URL` | Sentry processes, with a token that has `event:read`. The URL only for a self-hosted Sentry. |
| `OIDC_ISSUER`, `GOOGLE_CLIENT_ID` and the rest | Signing in. See [Sign-in and secrets](https://spoletta.com/docs/sign-in.md). |
| `MYSQL_PUBLISH` | Where MySQL is published. If another MySQL already listens on `127.0.0.1:3306`, set it to `[::1]:3306`. |

Scripts and agents can read `GIT_TOKEN` and the Sentry and Jira settings too, since they run on the worker with them. With Podman, the worker takes the Sentry and Jira settings from the shell, as it does model keys, and only the webapp, for Sentry processes, from .env.

## Day to day

| To  | Run |
| --- | --- |
| Update to the latest Spoletta | `git pull && podman compose up --build -d` |
| Pick up a changed pi config | `podman compose restart worker` |
| Stop it, keeping everything | `podman compose down` |
| Back up the database | `podman compose exec mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction app' > spoletta.sql` |

> **down -v deletes your flows:** `podman compose down -v` removes the volumes too: the database with every flow and run, the files runs made, and the runs' checkouts.
