# With Docker

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

Run Spoletta on one machine with Docker Compose: MySQL, the webapp and the worker that runs agents and scripts, each in a container built from Spoletta's source.

## What you need

-   [Docker](https://docs.docker.com/get-docker/) with Compose v2: `docker compose version` prints a version. Docker Desktop has it.
-   `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=...
    ```
    
    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
    docker compose up --build -d
    ```
    
    The first build takes a few minutes. `docker compose ps` shows the three containers, and `docker 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 `docker compose up -d` to reach it from other machines.

## More settings

All go in `.env`, and take effect with `docker 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.

## Day to day

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

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