Skip to main content

Overview

Every step of Capsule setup that’s normally interactive in the TUI has a non-interactive CLI equivalent, so a fresh server can be fully provisioned from a playbook: install the binary, authenticate, add database connections, schedule backups, and install the systemd service. Secrets (your license key, database passwords, MongoDB URIs) are always passed as environment variables, never as plain command-line flags — so they never land in shell history, ps output, or Ansible’s own command logging.

Full example playbook

# roles/capsule/tasks/main.yml
- name: Install Capsule CLI
  ansible.builtin.shell: curl -fsSL https://trycapsule.xyz/install.sh | sudo sh
  args:
    creates: /usr/local/bin/capsule

- name: Authenticate with license key
  ansible.builtin.command: capsule auth
  environment:
    CAPSULE_LICENSE_KEY: "{{ vault_capsule_license_key }}"

- name: Add the production Postgres connection
  ansible.builtin.command: >
    capsule connection add
    --label prod-postgres
    --type postgres
    --host {{ postgres_host }}
    --port 5432
    --database myapp
    --user {{ postgres_user }}
  environment:
    CAPSULE_CONN_PASSWORD: "{{ vault_postgres_password }}"
  register: conn_add
  changed_when: "'Connection added' in conn_add.stdout"
  failed_when: conn_add.rc != 0 and 'already exists' not in conn_add.stderr

- name: Schedule nightly backups at 2am
  ansible.builtin.command: capsule schedule add --connection prod-postgres --cron "0 2 * * *"

- name: Install and start the systemd service
  ansible.builtin.command: capsule install-service
  args:
    chdir: /opt/capsule
  become: true

Step by step

1. Install the binary

- name: Install Capsule CLI
  ansible.builtin.shell: curl -fsSL https://trycapsule.xyz/install.sh | sudo sh
  args:
    creates: /usr/local/bin/capsule
creates: /usr/local/bin/capsule makes this idempotent — Ansible skips the install script entirely on subsequent runs once the binary exists.

2. Authenticate

- name: Authenticate with license key
  ansible.builtin.command: capsule auth
  environment:
    CAPSULE_LICENSE_KEY: "{{ vault_capsule_license_key }}"
Normally capsule auth prompts interactively for your license key. It also checks CAPSULE_LICENSE_KEY in the environment first — if set, it skips the prompt entirely and saves that key. Keep the key in Ansible Vault, never in plain YAML.

3. Add connections

- name: Add the production Postgres connection
  ansible.builtin.command: >
    capsule connection add
    --label prod-postgres
    --type postgres
    --host {{ postgres_host }}
    --port 5432
    --database myapp
    --user {{ postgres_user }}
  environment:
    CAPSULE_CONN_PASSWORD: "{{ vault_postgres_password }}"
  register: conn_add
  changed_when: "'Connection added' in conn_add.stdout"
  failed_when: conn_add.rc != 0 and 'already exists' not in conn_add.stderr
capsule connection add normally prompts for the password with input hidden. Setting CAPSULE_CONN_PASSWORD in the task’s environment skips that prompt. For MongoDB, use --type mongo and set CAPSULE_CONN_URI instead (see capsule connection for the full flag reference). Re-running add for a label that already exists fails with a connection named "..." already exists — the failed_when above treats that as a no-op so the playbook stays idempotent instead of erroring on every re-run.
If your plan’s connection limit is already reached, connection add fails immediately with an upgrade message before prompting for anything. Check capsule connection list if a play fails unexpectedly.

4. Schedule backups

- name: Schedule nightly backups at 2am
  ansible.builtin.command: capsule schedule add --connection prod-postgres --cron "0 2 * * *"
schedule add upserts by connection label — re-running it with the same cron expression is naturally idempotent, no special handling needed. See capsule schedule for pausing/resuming and other subcommands.

5. Install the service

- name: Install and start the systemd service
  ansible.builtin.command: capsule install-service
  args:
    chdir: /opt/capsule
  become: true
install-service must run as root (become: true) and creates a systemd unit that starts on boot and restarts on failure. chdir sets the daemon’s working directory — pick wherever you want its runtime state to live. See capsule service for details.

Backing up a dockerized database

If the target database runs in a container on the same host, add --docker <container_name> to the connection:
- name: Add a connection to a dockerized Postgres
  ansible.builtin.command: >
    capsule connection add
    --label prod-postgres-docker
    --type postgres
    --host localhost
    --port 5432
    --database myapp
    --user postgres
    --docker myapp_postgres
  environment:
    CAPSULE_CONN_PASSWORD: "{{ vault_postgres_password }}"
This makes every backup run as docker exec -i myapp_postgres pg_dump ... instead of natively — you don’t need the Postgres client tools installed on the host, just Docker itself. Run capsule doctor afterward to confirm everything required is reachable.

Updating a schedule

schedule add upserts by connection label, so changing a cron expression later is just re-running the same task with a new value — no separate “update” step needed:
- name: Change the backup schedule to every 6 hours
  ansible.builtin.command: capsule schedule add --connection prod-postgres --cron "0 */6 * * *"
This is naturally idempotent and safe to leave in the playbook permanently — if the cron expression in your vars file changes, re-running the play applies it; if it’s unchanged, the daemon just re-reads the same value.
Don’t use capsule schedule remove + add to change a cron expression — remove permanently deletes the schedule (you’d lose its last-run history), and it’s unnecessary churn. add alone already updates it in place.

Rotating database credentials

Unlike connections, capsule connection add refuses to touch an existing label (a connection named "..." already exists) — it’s meant for first-time creation, not updates. Use capsule connection update instead, which changes fields in place on the same connection ID, so its attached schedule is left untouched:
- name: Rotate the Postgres password
  ansible.builtin.command: capsule connection update --connection prod-postgres
  environment:
    CAPSULE_CONN_PASSWORD: "{{ vault_postgres_password }}"
  no_log: true
Set no_log: true on this task — otherwise Ansible may echo the task’s resolved environment into its own output/logs on failure, which would leak the new password into your CI logs. This also works for a host change, without touching the password:
- name: Point the connection at the new replica host
  ansible.builtin.command: capsule connection update --connection prod-postgres --host db-replica.example.com
Don’t use capsule connection remove + add to rotate credentials — remove also deletes the connection’s schedule (see capsule connection remove), so you’d silently lose your backup cadence along with it. update is the safe, idempotent path for changing anything about a connection you’ve already created.
For MongoDB, rotate the whole URI the same way:
- name: Rotate the MongoDB connection URI
  ansible.builtin.command: capsule connection update --connection prod-mongo
  environment:
    CAPSULE_CONN_URI: "{{ vault_mongo_uri }}"
  no_log: true

Verifying the setup

- name: Verify required database tools are installed
  ansible.builtin.command: capsule doctor
  register: doctor_result
  changed_when: false

- name: Confirm the schedule was created
  ansible.builtin.command: capsule schedule list
  register: schedule_list
  changed_when: false
Run the whole role against a staging host first and check capsule connection list / capsule schedule list before rolling it out to production servers.

See also