Polycrate API for Teams: Centralized Monitoring and Remote Triggering
TL;DR The Polycrate API transforms individual workspaces into a team platform: all workspaces, …
Diese Serie zeigt Schritt für Schritt, wie Ansible mit Polycrate zu einer strukturierten, teilbaren und compliance-fähigen Automatisierungsplattform wird – von den Grundlagen bis zu Enterprise-Szenarien.
inventory.yml in the workspace root to centrally manage all Linux servers—without needing your own Ansible setup on your laptop.~/.ssh/ key): Polycrate creates it when you run workspace init with --with-ssh-keys, stores it under artifacts/secrets/, and wires Ansible so the private key is used without ansible_ssh_private_key_file in the inventory—unlike plain Ansible.ansible-playbook -i inventory.yml hardening.yml, with Polycrate a polycrate run linux-baseline hardening suffices—inclusive of container toolchain and central inventory.yml (plain YAML, no Jinja2 in the inventory; see Ansible integration).A typical day as a Linux admin: dozens (or hundreds) of Ubuntu servers, security updates looming, requests from departments, logs filling up—and you actually need a new script for the next special case.
Ansible helps tremendously, but in practice, you probably know these stumbling blocks:
Polycrate addresses exactly this: It packages Ansible in a container, brings a complete, reproducible toolchain, and provides a fixed structure with workspaces and blocks. Locally, you only need Polycrate—no Ansible, no Python chaos, no global ansible.cfg experiments.
In this post, we build a small but practical setup:
inventory.ymlworkspace init / --with-ssh-keys), not personal keys from ~/.ssh/The entry point in Polycrate is the workspace. It bundles:
workspace.poly – configuration of the workspaceinventory.yml – the central Ansible inventory for all blocksblocks/ – your Ansible blocks (including block.poly and playbooks)artifacts/secrets/ – including SSH keys generated for the workspace (not copies from ~/.ssh/)Create a directory and initialize the workspace with the CLI—you do not need to hand-write workspace.poly or SSH keys. See Initialize workspace and SSH keys (best practices).
Polycrate uses $HOME/.polycrate/workspaces/ as the default location for workspaces. Recommended (not mandatory): a folder hierarchy $HOME/.polycrate/workspaces/<organization>/<workspace>/ where the segments match organization and name in workspace.poly, which keeps workspaces cleanly separated (see Best practices – directory layout).
mkdir -p "$HOME/.polycrate/workspaces/acme/acme-corp-automation"
cd "$HOME/.polycrate/workspaces/acme/acme-corp-automation"
polycrate workspace init --with-name acme-corp-automation --with-organization acme --with-ssh-keysThis creates workspace.poly and, with --with-ssh-keys, a workspace-specific key pair under artifacts/secrets/. Then add the block to workspace.poly for this walkthrough:
# workspace.poly (snippet – add blocks)
blocks:
- name: linux-baseline
from: cargo.ayedo.cloud/ayedo/infra/linux-baseline:0.3.1
config:
default_target_group: "linux_all"name and organization are already set by workspace init; under blocks you declare instances. The instance is referenced by name: (e.g. polycrate run linux-baseline …); by convention, name matches the directory name under blocks/ (the path mirrors the OCI reference).cargo.ayedo.cloud. You do not need to run polycrate blocks pull first: if the block is missing locally, Polycrate detects that when you run polycrate run … and asks whether to install the block from the registry automatically. After that, the unpacked tree lives under blocks/cargo.ayedo.cloud/ayedo/infra/linux-baseline/. In from: use the full registry reference including the version tag (OCI sharing instead of a short template name; see Inheritance).The inventory is always in the workspace root as inventory.yml. Polycrate automatically sets the environment variable ANSIBLE_INVENTORY; you no longer need to specify a -i parameter.
Example:
# inventory.yml
all:
children:
linux_all:
hosts:
srv01.acme-corp.com:
ansible_user: ubuntu
srv02.acme-corp.com:
ansible_user: ubuntu
webservers:
hosts:
srv01.acme-corp.com:
dbservers:
hosts:
srv02.acme-corp.com:Key points:
linux_all is our default group for all Linux servers.webservers and dbservers allow targeted actions.inventory.yml is static YAML—no Jinja2/templating (neither {{ … }} nor workspace.secrets[…]). In Polycrate, Jinja2 applies to Ansible playbooks and block templates, not the inventory file; see Configuration and Ansible integration – inventory.ansible_ssh_private_key_file: Polycrate configures Ansible to use the workspace key automatically—a practical difference from plain Ansible.With plain Ansible you often maintain per-admin key paths in the inventory or ansible.cfg. With Polycrate the inventory stays lean and key handling is integrated into the runtime.
SSH is the standard way to reach Linux servers with Ansible. In many teams, private keys are on laptops or USB sticks—suboptimal from a compliance and security perspective.
Polycrate takes a different approach:
~/.ssh/. That keeps automation identity separate from user identity (see Best practices – SSH keys).polycrate workspace init --with-ssh-keys and stored under artifacts/secrets/ (including id_rsa, id_rsa.pub).ansible_ssh_private_key_file in inventory.yml. Paths to secrets are available in playbooks via workspace.secrets[...] when you must reference files explicitly (not in the inventory).Add this workspace’s public key (artifacts/secrets/id_rsa.pub) to authorized_keys for the target user on each host, like any dedicated automation key. Do not copy your personal private key from ~/.ssh/id_rsa (or similar) into the workspace.
If a legacy workspace has no keys yet, use polycrate create ssh-keys—do not copy personal private keys into artifacts/secrets/.
Details: SSH in Polycrate.
Now to the actual daily business: keeping systems up to date and checking if everything is running.
We create a block linux-baseline that contains a basic playbook for:
logrotate is properly configured and activated)Create the block folder:
mkdir -p blocks/linux-baseline
cd blocks/linux-baselineCreate a block.poly:
# blocks/linux-baseline/block.poly
name: linux-baseline
version: 0.1.0
kind: generic
config:
default_target_group: "linux_all"
services_to_check:
- ssh
- cron
actions:
- name: base
playbook: base.yml
description: "Update packages, check services, ensure log rotation"
- name: hardening
playbook: hardening.yml
description: "Configure SSH hardening, UFW, and fail2ban"config defines block defaults that you can override in the workspace if needed.actions are named entry points—you don’t have to remember complicated Ansible CLI calls.The general block syntax is described in the documentation on Blocks.
base.ymlNow the Ansible playbook that is executed as action base:
# blocks/linux-baseline/base.yml
- name: Linux Basic Management
hosts: "{{ block.config.default_target_group }}"
become: true
gather_facts: true
vars:
logrotate_package: logrotate
tasks:
- name: Update package index
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install security updates
ansible.builtin.apt:
upgrade: dist
register: upgrade_result
- name: Show changed packages
ansible.builtin.debug:
var: upgrade_result
when: upgrade_result is changed
- name: Ensure logrotate package
ansible.builtin.apt:
name: "{{ logrotate_package }}"
state: present
- name: Ensure logrotate service (cron.daily)
ansible.builtin.stat:
path: /etc/cron.daily/logrotate
register: logrotate_cron
- name: Create logrotate cron job if not present
ansible.builtin.copy:
dest: /etc/cron.daily/logrotate
content: |
#!/bin/sh
/usr/sbin/logrotate /etc/logrotate.conf
owner: root
group: root
mode: "0755"
when: not logrotate_cron.stat.exists
- name: Check important services
ansible.builtin.service_facts:
- name: Output status of configured services
ansible.builtin.debug:
msg: "Service {{ item }} is {{ ansible_facts.services[item + '.service'].state | default('unknown') }}"
loop: "{{ block.config.services_to_check }}"
when: "item + '.service' in ansible_facts.services"Key features:
hosts uses the target group configured in the block or workspace (linux_all).apt with state: present or upgrade is repeatable.copy only writes if the file is missing or changes.You can execute the whole thing with:
cd "$HOME/.polycrate/workspaces/acme/acme-corp-automation"
polycrate run linux-baseline basePolycrate starts a container with Ansible and the complete toolchain. You don’t need Ansible installed locally; this avoids the usual dependency problem (different Python/Ansible versions, system-wide packages, etc.). See also the Ansible Integration of Polycrate.
Now it gets security-relevant: securing SSH, activating the firewall, configuring fail2ban. We use the same block definition (linux-baseline), but a second action hardening with its own playbook hardening.yml.
hardening.yml# blocks/linux-baseline/hardening.yml
- name: Linux Hardening
hosts: "{{ block.config.default_target_group }}"
become: true
gather_facts: true
vars:
sshd_config_path: /etc/ssh/sshd_config
allowed_ssh_users:
- ubuntu
ufw_allowed_ports:
- "22/tcp"
- "80/tcp"
- "443/tcp"
tasks:
- name: Disable root login via SSH
ansible.builtin.lineinfile:
path: "{{ sshd_config_path }}"
regexp: "^PermitRootLogin"
line: "PermitRootLogin no"
state: present
create: false
backup: yes
- name: Disable password authentication via SSH
ansible.builtin.lineinfile:
path: "{{ sshd_config_path }}"
regexp: "^PasswordAuthentication"
line: "PasswordAuthentication no"
state: present
create: false
backup: yes
- name: Set allowed SSH users
ansible.builtin.lineinfile:
path: "{{ sshd_config_path }}"
regexp: "^AllowUsers"
line: "AllowUsers {{ allowed_ssh_users | join(' ') }}"
state: present
create: false
backup: yes
- name: Restart SSH service
ansible.builtin.service:
name: ssh
state: restarted
- name: Install UFW
ansible.builtin.apt:
name: ufw
state: present
update_cache: true
- name: "Default policy: incoming deny, outgoing allow"
community.general.ufw:
direction: incoming
policy: deny
- name: Allow outgoing traffic
community.general.ufw:
direction: outgoing
policy: allow
- name: Allow required ports
community.general.ufw:
rule: allow
port: "{{ item }}"
loop: "{{ ufw_allowed_ports }}"
- name: Enable UFW
community.general.ufw:
state: enabled
- name: Install fail2ban
ansible.builtin.apt:
name: fail2ban
state: present
- name: Set basic fail2ban configuration
ansible.builtin.copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
filter = sshd
action = iptables[name=SSH, port=ssh, protocol=tcp]
logpath = /var/log/auth.log
maxretry = 5
owner: root
group: root
mode: "0644"
- name: Restart and enable fail2ban service
ansible.builtin.service:
name: fail2ban
state: restarted
enabled: trueNotes:
community.general.ufw module (via Ansible Galaxy).sshd_config changes via lineinfile are idempotent.Run against all Linux servers:
cd "$HOME/.polycrate/workspaces/acme/acme-corp-automation"
polycrate run linux-baseline hardeningTo harden only web servers, override the block config in the workspace:
# workspace.poly (adjusted snippet)
name: acme-corp-automation
organization: acme
blocks:
- name: linux-baseline
from: cargo.ayedo.cloud/ayedo/infra/linux-baseline:0.3.1
config:
default_target_group: "webservers"Both base and hardening then target the webservers group from the central inventory—without changing the Ansible playbooks.
What would this look like without Polycrate?
With plain Ansible you would typically run:
# Base playbook
ansible-playbook -i inventory.yml base.yml
# Hardening
ansible-playbook -i inventory.yml hardening.ymlOn your workstation you would also need:
pip, apt, venv, …)ansible.cfg (e.g. for SSH args, inventory path)When a colleague needs the same setup, the cycle repeats: align versions, adjust paths, possibly another OS on their laptop.
With Polycrate this reduces to:
polycrate run linux-baseline base
polycrate run linux-baseline hardeningBenefits for you as a Linux admin:
inventory.yml and a clear block model reduce playbook sprawl.Polycrate brings Platform Engineering concepts into day-to-day admin work without forcing you to learn Kubernetes or complex CI/CD first.
No. Polycrate always runs Ansible inside a container. The required binaries (Ansible, Python, additional tools) are part of the container image you can customize via Dockerfile.poly or a shell script. That avoids typical version conflicts between admin workstations. See the documentation on Ansible integration in Polycrate.
Your SSH keys live under artifacts/secrets/. Optionally you can encrypt the entire workspace with age—when to do that and what it means for polycrate run is covered in Encrypting the workspace (after setup) at the end of this post. Basics: SSH in Polycrate.
Yes. In most cases you can drop existing playbooks almost unchanged as actions in a block—as we did with base.yml and hardening.yml. Use the central inventory.yml in the workspace and avoid local inventories or hosts: localhost with connection: local for SSH-based server management. Polycrate handles the container environment, SSH, secrets, and structured execution via blocks and actions.
More questions? See our FAQ.
With this setup you have a solid foundation:
inventory.yml for all Linux serversartifacts/secrets/ and Polycrate’s automatic Ansible integration (no key path in the inventory)linux-baseline blockpolycrate run, which starts Ansible in a container—without local dependenciesYou have moved from ad-hoc scripts and scattered playbooks toward reproducible, structured automation. The same ideas extend easily: more blocks for backup, monitoring agents, compliance scans, or integration with existing tools and processes.
ayedo helps teams make exactly this transition—from manual server care to a thoughtful automation and workspace structure that fits your processes, security requirements, and operations—whether you manage a few dozen Linux servers or a larger estate together with a central Platform Engineering team.
If you want to move your environment to Polycrate and Ansible in a structured way, evaluate concrete playbooks, or design a shared baseline, our server automation workshop is a good fit: Server automation workshop
After you have blocks, inventory, and polycrate run working, you can protect the workspace for idle storage or handoff using built-in age encryption:
polycrate workspace encryptImportant: An encrypted workspace is not suitable for day-to-day work with actions: polycrate run … requires an unlocked workspace. Encryption is therefore typically the last step when you are done with setup—or you decrypt explicitly before work (details: Workspace encryption).
TL;DR The Polycrate API transforms individual workspaces into a team platform: all workspaces, …
TL;DR Polycrate not only logs Action Runs (Ansible playbooks) but also SSH sessions, workspace …
TL;DR The Model Context Protocol (MCP) is an open standard: AI clients talk to helper programs over …