Getting started

Canonical develop-and-deploy chain (git-free): Develop and deploy. Git is optional — it is not the Polycrate process.

Installation

The Polycrate CLI install guide is here.

Create a workspace

Recommended path: $HOME/.polycrate/workspaces/<organization>/<workspace>/ — see Best practices.

mkdir -p $HOME/.polycrate/workspaces/acme/acme-demo-1
cd $HOME/.polycrate/workspaces/acme/acme-demo-1
polycrate workspace init --with-name acme-demo-1 --with-organization acme

If the workspace already exists in the Polycrate API, clone it locally instead of initializing again:

polycrate workspace clone acme/acme-demo-1
cd $HOME/.polycrate/workspaces/acme/acme-demo-1

Prerequisite: API in ~/.polycrate/polycrate.yml and SSH access to the Git remote. → Clone a workspace

Typical contents afterwards:

blocks/
artifacts/secrets/   # including SSH keys (id_rsa / id_rsa.pub)
workspace.poly

workspace.poly (excerpt):

name: acme-demo-1
organization: acme

Polycrate works best with Ansible. The following examples show what you can do with Polycrate and Ansible.

For the remaining steps we assume that:

  • a server exists and is reachable at 1.2.3.4
  • the workspace public SSH key is in the root user's authorized_keys on that server
  • port 22 is open for SSH

Create an inventory

Most Ansible tasks need a valid inventory. We create one and store it at artifacts/blocks/inventory/inventory.yml.

all:
  hosts:
    my-host:
      ansible_host: 1.2.3.4
      ansible_ssh_port: 22
      ansible_python_interpreter: "/usr/bin/python3"
      ansible_user: root
  children:
    my-hosts:
      hosts:
        my-host

This inventory has a host named my-host at 1.2.3.4 and a group my-hosts with my-host as its only member.

Save it as described in artifacts/blocks/inventory/inventory.yml:

mkdir -p artifacts/blocks/inventory

cat <<EOF > artifacts/blocks/inventory/inventory.yml

all:
  hosts:
    my-host:
      ansible_host: 1.2.3.4
      ansible_ssh_port: 22
      ansible_python_interpreter: "/usr/bin/python3"
      ansible_user: root
  children:
    my-hosts:
      hosts:
        my-host

EOF

Add an inventory block to the workspace

In Polycrate, blocks provide the actual workspace functionality. Blocks can contain any code; the best integration is with Ansible. One block might install Docker on Linux, another install Traefik with Docker Compose.

Blocks work like classes — they can be instantiated and inherit properties and functions (Actions).

Create a block by adding a directory under blocks/ with a valid block.poly (and optional code). The name key in that file is the block name in the workspace.

Blocks can also be derived from other blocks (parent/child). The child inherits the parent's working directory plus all properties and actions. Values defined on the child override the parent.

Blocks under blocks/ are available automatically and do not need a separate workspace entry. For the inventory we do not need a folder under blocks/ — it is enough to tell the workspace that a block named inventory exists.

# workspace.poly

name: acme-demo-1
organization: acme

blocks:
  - name: inventory
Every Polycrate run scans `artifacts/` and makes found artifacts available to the workspace.

Add a functional block

The inventory block currently only exposes an Ansible inventory for other blocks. Next we add a block that changes the server — it installs packages.

We need Ansible, so we create a new block:

mkdir -p blocks/install-packages

cat <<EOF > blocks/install-packages/block.poly

name: install-packages

config:
  packages:
    - curl

actions:
  - name: install
    playbook: install.yml

EOF

These commands create blocks/install-packages and blocks/install-packages/block.poly.

List workspace blocks with `polycrate block list`.

The file has an actions attribute — Actions are a core Polycrate concept. They let you define user-facing operations on a block, such as install and uninstall.

Create the install.yml playbook linked to the install action:

cat <<EOF > blocks/install-packages/install.yml
- name: Install packages
  hosts: all
  tasks:
    - name: Install packages
      ansible.builtin.package:
        name: "{{ item }}"
        state: present
      with_items: "{{ block.config.packages }}"
EOF

This playbook connects to every host in an Ansible inventory and installs every package in block.config.packages.

The snapshot exposes the current block as top-level `block` and the workspace as `workspace`. Inside Ansible you can read block config with `{{ block.SUB.PATH }}` ([Jinja](https://jinja.palletsprojects.com/en/3.1.x/), not [Go templates](https://pkg.go.dev/text/template)).

Run the action with polycrate run install-packages install.

That will fail until install-packages is wired to the inventory. Add an inventory key on the block in workspace.poly:

# workspace.poly

name: acme-demo-1
organization: acme

blocks:
  - name: inventory
  - name: packages
    from: install-packages
    inventory:
      from: inventory

Two relevant changes:

  1. We defined a packages block that derives from install-packages. Polycrate blocks can be used like classes and instantiated more than once. When an action on a derived block runs, Polycrate switches the workdir to the parent (install-packages) so that code can run. Config and actions of install-packages are merged with packages (the child wins).
  2. We linked packages to the inventory block's inventory. Ansible then reaches the real server when packages install runs.

Run an action

We now have a block that can install packages on a Linux host (curl in this example). Try it: polycrate run packages install

INFO[0000] Running action                                action=install block=packages txid=2fc57485-d1b4-43f7-b82b-533126fb3690 workspace=acme-demo-1

INFO[0000] Starting container                            action=install block=packages txid=2fc57485-d1b4-43f7-b82b-533126fb3690 workspace=acme-demo-1

INFO[0000] Pulling image: cargo.ayedo.cloud/library/polycrate:0.18.17 

PLAY [Install packages] ********************************************************

TASK [Gathering Facts] *********************************************************
ok: [my-host]

TASK [Install packages] ********************************************************
changed: [my-host] => (item=curl)

PLAY RECAP *********************************************************************
my-host                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

INFO[0010] Removing container                            action=install block=packages txid=2fc57485-d1b4-43f7-b82b-533126fb3690 workspace=acme-demo-1

We defined an install action that installs curl from block.poly on my-host.

To also install wget you can:

  1. Change the package list in the install-packages block file
  2. Override the package list in the workspace file

Because packages derives from install-packages, it inherits the package list (curl only). To add wget, override config.packages on packages:

# workspace.poly

name: acme-demo-1
organization: acme

blocks:
  - name: inventory
  - name: packages
    from: install-packages
    inventory:
      from: inventory
    config:
      packages:
        - curl
        - wget

Run the action again: polycrate run packages install

INFO[0000] Running action                                action=install block=packages txid=abd5b953-f3b7-4a93-901e-84b72f73e919 workspace=acme-demo-1

INFO[0000] Starting container                            action=install block=packages txid=abd5b953-f3b7-4a93-901e-84b72f73e919 workspace=acme-demo-1

INFO[0000] Pulling image: cargo.ayedo.cloud/library/polycrate:0.18.17 

PLAY [Install packages] ********************************************************

TASK [Gathering Facts] *********************************************************
ok: [my-host]

TASK [Install packages] ********************************************************
ok: [my-host] => (item=curl)
ok: [my-host] => (item=wget)

PLAY RECAP *********************************************************************
my-host                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

INFO[0008] Removing container                            action=install block=packages txid=abd5b953-f3b7-4a93-901e-84b72f73e919 workspace=acme-demo-1

Here wget was already installed, so Ansible reported no change — but it did evaluate wget in addition to curl.

Connect over SSH

Polycrate can open SSH to hosts in a workspace Ansible inventory. By default it loads the inventory block: polycrate ssh my-host.

You can keep more than one inventory. To reach a host from another inventory: polycrate ssh --block inventory2 my-host-2

→ Details: SSH

Fun with Ansible

Full integration (inventory, extra vars, patterns): Ansible.

Add the following playbook as a block and run it. It shows:

  1. The block variable as Ansible sees it (full current-block config)
  2. block.name (current block name)
  3. action.name (current action name)
  4. block.config (user config of the current block)
  5. workspace (full workspace config, including top-level block and action)
  6. How to read another block's config
  7. How to read the same config key from several blocks at once
# blocks/ansible/playbook.yml
- name: "Debug workspace"
  hosts: localhost
  tasks:
    - name: Show current block
      ansible.builtin.debug:
        var: block

    - name: Show current block name
      ansible.builtin.debug:
        var: block.name

    - name: Show current action name
      ansible.builtin.debug:
        var: action.name

    - name: Show current block config
      ansible.builtin.debug:
        var: block.config

    - name: Show workspace
      ansible.builtin.debug:
        var: workspace

    - name: Get config from block with name 'packages'
      ansible.builtin.debug:
        var: (workspace.blocks | selectattr('name', 'match', 'packages') | first).config

    - name: Show 'packages' user-config  of all blocks that have it
      ansible.builtin.debug:
        var: item
      loop: "{{ workspace | community.general.json_query('blocks[*].config.packages') }}"

Wrap-up

That is enough for a first impression. Next: Develop and deploy · Ansible · Blocks · Best practices