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
rootuser'sauthorized_keyson 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:
- We defined a
packagesblock that derives frominstall-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 ofinstall-packagesare merged withpackages(the child wins). - We linked
packagesto theinventoryblock's inventory. Ansible then reaches the real server whenpackagesinstallruns.
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:
- Change the package list in the
install-packagesblock file - 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:
- The
blockvariable as Ansible sees it (full current-block config) block.name(current block name)action.name(current action name)block.config(user config of the current block)workspace(full workspace config, including top-levelblockandaction)- How to read another block's config
- 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