Linux Servers on Autopilot: System Management with Polycrate and Ansible
Fabian Peter 12 Minuten Lesezeit

Linux Servers on Autopilot: System Management with Polycrate and Ansible

Automate Linux server management with Polycrate and Ansible
Ganze Serie lesen (24 Artikel)

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.

  1. Install Polycrate and Build Your First Ansible Block in 15 Minutes
  2. Blocks, Actions, and Workspaces: The Modular Principle of Polycrate
  3. Linux Servers on Autopilot: System Management with Polycrate and Ansible
  4. Nginx and Let's Encrypt as a Reusable Polycrate Block
  5. Managing Docker Stacks on Linux Servers with Polycrate
  6. Many Servers, One Truth: Multi-Server Management with Polycrate Inventories
  7. Windows Automation with Polycrate: Ansible and WinRM Without Pain
  8. Windows Software Deployment without SCCM: Chocolatey and Ansible
  9. Hybrid Automation: Windows and Linux in the Same Polycrate Workspace
  10. Deploy Kubernetes Apps from the PolyHub: From Idea to Deployment in Minutes
  11. Creating Your Own Kubernetes App as a Polycrate Block: A Step-by-Step Guide
  12. Multi-Cluster Kubernetes with Polycrate: Why One Cluster, One Workspace
  13. SSH Sessions and kubectl Debugging: Polycrate as an Operations Tool
  14. Helm Charts as a Polycrate Block: More Control Over Chart Deployments
  15. Policy as Code: Automating Compliance Requirements with Polycrate
  16. Workspace Encryption: Managing Secrets in GDPR Compliance – Without External Tooling
  17. Managing Raspberry Pi and Edge Nodes with Polycrate in IoT and Edge Computing
  18. Enterprise Automation: Building, Versioning, and Sharing Blocks Within Teams
  19. Polycrate MCP: Connecting AI Assistants with Live Infrastructure Context
  20. Polycrate vs. plain Ansible: What You Gain – and Why It's Worth It
  21. The Polycrate Ecosystem: PolyHub, API, MCP, and the Future of Automation
  22. Your First Productive Polycrate Workspace: A Checklist for Getting Started
  23. Auditable Operations: SSH Sessions and CLI Activities with Polycrate API
  24. Polycrate API for Teams: Centralized Monitoring and Remote Triggering

TL;DR

  • With Polycrate, you create a single inventory.yml in the workspace root to centrally manage all Linux servers—without needing your own Ansible setup on your laptop.
  • Each workspace should use its own SSH key pair (not your personal ~/.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.
  • A basic playbook handles package updates, service checks, and log rotation; a dedicated hardening block takes care of SSH hardening, firewall (ufw), and fail2ban—idempotent and executable as often as needed.
  • Compared to 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).
  • ayedo supports you with Polycrate, Platform Engineering expertise, and workshops on Polycrate and automation (for example Polycrate Essentials) to set up your Linux server management in a structured and repeatable manner.

Starting Point: Many Linux Servers, Little Time

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:

  • Different Python/Ansible versions on admin workstations
  • Inventories scattered across numerous repos and directories
  • SSH keys distributed locally on laptops
  • Playbook sprawl without clear structure

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:

  • A workspace with a central inventory.yml
  • SSH: a dedicated key pair per workspace (workspace init / --with-ssh-keys), not personal keys from ~/.ssh/
  • A basic playbook for updates, service checks, and log rotation
  • A hardening block for SSH, ufw, and fail2ban
  • Execution against multiple server groups—idempotent and repeatable

Creating a Workspace and Centralizing Inventory

The entry point in Polycrate is the workspace. It bundles:

  • workspace.poly – configuration of the workspace
  • inventory.yml – the central Ansible inventory for all blocks
  • blocks/ – your Ansible blocks (including block.poly and playbooks)
  • artifacts/secrets/ – including SSH keys generated for the workspace (not copies from ~/.ssh/)

Workspace Basics

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-keys

This 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).
  • The block is published under 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).

Central Inventory: One Truth for All Blocks

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.
  • Subgroups like webservers and dbservers allow targeted actions.
  • inventory.yml is static YAMLno 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.
  • You do not need to set the workspace’s SSH private key via 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.


Secure SSH Keys in the Workspace

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:

  • One key pair per workspace—not your personal private key from ~/.ssh/. That keeps automation identity separate from user identity (see Best practices – SSH keys).
  • Keys are created by polycrate workspace init --with-ssh-keys and stored under artifacts/secrets/ (including id_rsa, id_rsa.pub).
  • Polycrate exposes them in the container and wires Ansible automatically to the workspace key—you do not put 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).

Install the public key on target servers

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.


Basic Playbook: Update Packages, Check Services, Rotate Logs

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:

  • Package updates (Ubuntu servers)
  • Service status checks
  • Log rotation (ensuring logrotate is properly configured and activated)

Block Structure

Create the block folder:

mkdir -p blocks/linux-baseline
cd blocks/linux-baseline

Create 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.
  • This block is local—you could later push it to a registry and reuse it in other workspaces (keyword: Sharable Automation via OCI Registry and PolyHub).

The general block syntax is described in the documentation on Blocks.

Basic Playbook base.yml

Now 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).
  • All tasks are idempotent:
    • apt with state: present or upgrade is repeatable.
    • copy only writes if the file is missing or changes.
  • You can run the playbook as often as you like without risking breaking anything—a core advantage of Ansible.

You can execute the whole thing with:

cd "$HOME/.polycrate/workspaces/acme/acme-corp-automation"
polycrate run linux-baseline base

Polycrate 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.


Hardening as a Separate Block Action: SSH, UFW, fail2ban

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 Playbook 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: true

Notes:

  • UFW uses the community.general.ufw module (via Ansible Galaxy).
  • All sshd_config changes via lineinfile are idempotent.
  • fail2ban is configured repeatably—re-running applies defined content without uncontrolled drift.

Run against all Linux servers:

cd "$HOME/.polycrate/workspaces/acme/acme-corp-automation"
polycrate run linux-baseline hardening

To 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.


Polycrate vs. plain Ansible in daily use

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.yml

On your workstation you would also need:

  • A matching Ansible version (often via pip, apt, venv, …)
  • Python in a compatible version
  • A properly configured ansible.cfg (e.g. for SSH args, inventory path)
  • Secure handling of your SSH key on the local system

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 hardening

Benefits for you as a Linux admin:

  • No local Ansible install—Polycrate always runs Ansible in a container and avoids the classic dependency problem.
  • One central inventory.yml and a clear block model reduce playbook sprawl.
  • Workspace contents (including secrets) can be protected for idle state and team sharing—see Encrypting the workspace (after setup) at the end of this post.
  • What you build once as a block can be versioned, pushed to a registry, and reused elsewhere—internally or via PolyHub, in the spirit of sharable automation.

Polycrate brings Platform Engineering concepts into day-to-day admin work without forcing you to learn Kubernetes or complex CI/CD first.


Frequently asked questions

Do I still need Ansible installed locally if I use Polycrate?

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.

How secure are my SSH keys in the workspace?

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.

Can I reuse my existing Ansible playbooks?

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.


Putting it into practice

With this setup you have a solid foundation:

  • One central workspace with a single inventory.yml for all Linux servers
  • Clean SSH key handling via artifacts/secrets/ and Polycrate’s automatic Ansible integration (no key path in the inventory)
  • A base playbook for updates, service checks, and log rotation
  • A hardening playbook for SSH, UFW, and fail2ban, exposed as actions on the linux-baseline block
  • Simple, repeatable execution via polycrate run, which starts Ansible in a container—without local dependencies

You 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


Encrypting the workspace (after setup)

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 encrypt

Important: 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).

Ähnliche Artikel