TL;DR
- Most environments are hybrid: Windows servers for AD, file services, and specialized applications, Linux for web, databases, and automation – separate automation increases complexity and risk.
- With Polycrate, you create a shared YAML inventory with the groups
linux_groupandwindows_group, encapsulate Linux and Windows playbooks in their own blocks, and manage everything from one workspace. - Shared non-sensitive values (e.g. DNS/NTP) go in each block’s
configinworkspace.poly– explicitly per block (in the hybrid example the same values twice, without YAML anchors). There is no Jinja templating in.polyfiles.
Files (SSH keys, kubeconfig, …) live underartifacts/secrets/and are encrypted with the workspace; sensitive block configuration such aswin_admin_passwordbelongs insecrets.poly(not asartifacts/secrets/win_admin_password) – see Workspace encryption. - A workflow orchestrates hybrid maintenance: first patch Linux servers, then Windows hosts – all with simple
polycrate workflows run …commands, without needing to install Ansible, Python, or pywinrm locally. - ayedo supports teams with field-tested hybrid workspaces, workshops, and reference blocks (including PolyHub registry), enabling Windows and Linux admins to work together on a structured, compliance-capable automation basis.
Hybrid Reality: Windows and Linux Belong Together
If you work in a larger company, your world usually looks like this:
- Active Directory, file servers, many specialized applications: Windows Server
- Web servers, reverse proxies, databases, monitoring, CI/CD: Linux
In many teams, these worlds end up in separate silos:
- own Ansible setup for Linux on a Linux admin VM
- own Ansible setup (or no Ansible at all) for Windows on a separate machine
- different Python versions, different modules, different standards
Polycrate turns this around: one workspace, two blocks (Linux and Windows), one inventory, one workflow. And everything runs in a container, which takes away the classic dependency problem with Ansible:
- no local Python or Ansible chaos
- no extra pywinrm installation for Windows modules
- reproducible toolchain on every developer or admin machine
In this post, we build just such a hybrid workspace – including inventory, blocks, playbooks, secrets, and workflow.
One Workspace for Everything: workspace.poly with Linux and Windows Block
The workspace is the central hub. Here you define:
- block instances (Linux and Windows management), including each block’s
config - optional workspace-wide settings under
configinworkspace.poly(e.g. container image – see Configuration), not as a free-form bag for arbitrary automation variables - a workflow that connects both worlds
There is no template substitution in workspace.poly or block.poly (no Jinja2); values are literal YAML or merged from secrets.poly. See Important limitations.
workspace.poly – set DNS/NTP as literal values in each block’s config (here the same values in linux-mgmt and windows-mgmt – no YAML anchors). SSH: Polycrate configures the Ansible environment in the action container (including ANSIBLE_PRIVATE_KEY_FILE) – you do not put ssh_private_key in block config or ansible_ssh_private_key_file in the playbook.
name: acme-corp-automation
organization: acme
blocks:
- name: linux-mgmt
from: registry.acme-corp.com/acme/infra/linux-mgmt:0.1.0
config:
dns_server: "10.0.0.10"
ntp_server: "time.acme-corp.com"
- name: windows-mgmt
from: registry.acme-corp.com/acme/infra/windows-mgmt:0.1.0
config:
dns_server: "10.0.0.10"
ntp_server: "time.acme-corp.com"
win_admin_user: "ACME\\ansible-svc"
workflows:
- name: hybrid-maintenance
steps:
- name: update-linux
block: linux-mgmt
action: update-linux
- name: update-windows
block: windows-mgmt
action: update-windows
secrets.poly (sensitive block configuration, merged with workspace.poly and overrides on key clash):
blocks:
- name: windows-mgmt
config:
win_admin_password: "…"
The WinRM password is not an artifact path: it lives under blocks[].config in secrets.poly, not as a file artifacts/secrets/win_admin_password. That matches Workspace encryption for secrets.poly (sensitive block configuration) versus files under artifacts/secrets/.
Important:
workspace.polydoes have aconfigkey, but it is meant for workspace/toolchain settings (e.g.config.image), not for arbitrary global variables such as DNS or inventory parameters – those belong in per-blockconfig(andsecrets.polywhen sensitive).- Merge order (lowest → highest precedence):
block.poly→workspace.poly→secrets.poly– see Configuration. from:contains the full OCI registry reference including the version tag (like container images);registry.acme-corp.comis an example (not a reference to any specific production registry).- Unpacked blocks live under
blocks/registry.acme-corp.com/acme/infra/linux-mgmt/and…/windows-mgmt/respectively (directory tree mirrors the registry path). - Block
configmerge and inheritance: inheritance and workspaces. - The workflow
hybrid-maintenancesequentially calls Linux and Windows actions.
Details: workspaces and workflows.
A Shared Inventory: linux_group and windows_group
Polycrate always uses a YAML inventory in the workspace root under inventory.yml. No INI files, no per-block inventory.
As in the multi-server article, every host appears once under all.hosts; under children you only assign them to groups so Ansible and polycrate ssh share the same canonical host list. Put shared Linux defaults under all.vars; WinRM settings belong on the Windows group because they apply only there.
Our hybrid inventory with two groups:
all:
vars:
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
ansible_python_interpreter: /usr/bin/python3
hosts:
linux01.acme-corp.com:
ansible_user: ubuntu
linux02.acme-corp.com:
ansible_user: ubuntu
win01.acme-corp.com: {}
win02.acme-corp.com: {}
children:
linux_group:
hosts:
linux01.acme-corp.com:
linux02.acme-corp.com:
windows_group:
hosts:
win01.acme-corp.com:
win02.acme-corp.com:
vars:
ansible_connection: winrm
ansible_winrm_transport: credssp
ansible_winrm_server_cert_validation: ignore
A few points about this:
- Linux hosts: SSH user per host under
all.hosts(or as a default inall.vars). Polycrate supplies the private key to Ansible via container environment variables (ANSIBLE_PRIVATE_KEY_FILE, etc.) – nossh_private_keyinworkspace.polyand noansible_ssh_private_key_filein the playbook. - Windows hosts: Connection parameters (
ansible_connection: winrmetc.) are in thevarsof groupwindows_group. - Username and password for WinRM do not go into the inventory but are set in the playbook from encrypted secrets via
block.config.win_admin_userandblock.config.win_admin_password.
Polycrate automatically sets the environment variable ANSIBLE_INVENTORY when starting the action, so you don't need to append -i inventory.yml to the Ansible CLI – Polycrate takes care of that for you. More on this in the Ansible integration.
Secrets Done Right: SSH Key and WinRM Password
Hybrid environments also mean hybrid authentication:
- SSH: Key material lives under
artifacts/secrets/(e.g.id_rsa) and is encrypted with the workspace. For Ansible, Polycrate wires access in the action container via environment (ANSIBLE_PRIVATE_KEY_FILE) – no explicitssh_private_keyinworkspace.polyand noansible_ssh_private_key_filein the playbook. - WinRM: The username can live in block
configinworkspace.poly; put the password insecrets.polyunder the matching block (windows-mgmt), not inworkspace.poly, so it merges intoblock.configand is not committed in plain text (or use certificates instead of a password, depending on your setup).
polycrate workspace encrypt encrypts secrets.poly and files under artifacts/secrets/ (see Workspace encryption). You activate and manage this via the CLI, for example:
# Encrypt secrets (e.g., before a Git commit)
polycrate workspace encrypt
# Decrypt for working
polycrate workspace decrypt
In Ansible playbooks (not in .poly files), you use block.config.* – values come from the merged block.poly + workspace.poly + secrets.poly.
This achieves two important things:
- Compliance-friendly storage: Secrets are not in plain text in the Git repo – important especially in the context of GDPR (effective since 25.05.2018) and internal security policies.
- Good UX/DX: No external vault is needed, no complicated integration. Polycrate brings the workspace encryption feature with age directly (see Workspace encryption).
Linux Block: linux-mgmt with OS-specific Tasks
Let's start with the Linux part. The block encapsulates everything you want to do for Linux servers – here as an example:
- package-based update (apt or yum)
- DNS and NTP basic configuration
In block.poly, name must be the full registry URL – the same string as in from: in workspace.poly, without the version tag (:0.1.0). In short: from: = name + : + tag. That is not the same identifier as the block instance linux-mgmt in workspace.poly. The file lives at blocks/registry.acme-corp.com/acme/infra/linux-mgmt/block.poly (directory tree mirrors the registry path).
# name = from: without tag (full registry path)
name: "registry.acme-corp.com/acme/infra/linux-mgmt"
version: 0.1.0
kind: generic
config:
dns_server: ""
ntp_server: ""
actions:
- name: update-linux
description: Update Linux servers and set basic configuration
playbook: linux_update.yml
The associated Ansible playbook blocks/registry.acme-corp.com/acme/infra/linux-mgmt/linux_update.yml:
- name: Update Linux servers and set basic config
hosts: linux_group
become: true
gather_facts: true
vars:
dns_server: "{{ block.config.dns_server }}"
ntp_server: "{{ block.config.ntp_server }}"
tasks:
- name: Update package list (Debian family)
ansible.builtin.apt:
update_cache: true
when: ansible_os_family == 'Debian'
- name: Upgrade packages (Debian family)
ansible.builtin.apt:
upgrade: dist
when: ansible_os_family == 'Debian'
- name: Upgrade packages (RedHat family)
ansible.builtin.yum:
name: "*"
state: latest
when: ansible_os_family == 'RedHat'
- name: Set DNS server in /etc/resolv.conf
ansible.builtin.lineinfile:
path: /etc/resolv.conf
regexp: '^nameserver'
line: "nameserver {{ dns_server }}"
when: ansible_os_family in ['Debian', 'RedHat']
- name: Install NTP package (Debian family)
ansible.builtin.package:
name: chrony
state: present
when: ansible_os_family == 'Debian'
- name: Configure NTP server in chrony.conf
ansible.builtin.lineinfile:
path: /etc/chrony/chrony.conf
regexp: '^server'
line: "server {{ ntp_server }} iburst"
when: ansible_os_family == 'Debian'
- name: Restart chrony
ansible.builtin.service:
name: chrony
state: restarted
enabled: true
when: ansible_os_family == 'Debian'
Key points:
hosts: linux_group– we work on the Linux hosts from the shared inventory, not onlocalhost.ansible_os_familycontrols OS-specific tasks (when:conditions). This allows you to handle Debian, RedHat, and others in the same playbook.- DNS and NTP come from merged
block.config.*. SSH: Polycrate setsANSIBLE_PRIVATE_KEY_FILEin the container – nossh_private_keyinworkspace.polyand noansible_ssh_private_key_filein the playbook. See Ansible integration.
You can execute the action with:
polycrate run linux-mgmt update-linux
You don't have to worry about the local installation of Ansible, Python, or SSH client – Polycrate starts a container with a complete toolchain (including Ansible, Python, SSH), as described in the Best Practices.
Windows Block: windows-mgmt with WinRM and ansible_os_family
The second block takes care of Windows hosts:
- Install Windows updates
- DNS and NTP basic configuration (via Ansible modules and PowerShell)
blocks/registry.acme-corp.com/acme/infra/windows-mgmt/block.poly – name again as the full registry path without tag, analogous to the Linux block:
# name = from: without tag (full registry path)
name: "registry.acme-corp.com/acme/infra/windows-mgmt"
version: 0.1.0
kind: generic
config:
dns_server: ""
ntp_server: ""
win_admin_user: ""
win_admin_password: ""
actions:
- name: update-windows
description: Update Windows servers and set basic configuration
playbook: windows_update.yml
The playbook blocks/registry.acme-corp.com/acme/infra/windows-mgmt/windows_update.yml:
- name: Update Windows hosts and set basic config
hosts: windows_group
gather_facts: true
vars:
ansible_user: "{{ block.config.win_admin_user }}"
ansible_password: "{{ block.config.win_admin_password }}"
dns_server: "{{ block.config.dns_server }}"
ntp_server: "{{ block.config.ntp_server }}"
tasks:
- name: Check reachability
ansible.windows.win_ping: {}
- name: Install Windows updates
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
reboot: yes
- name: Set DNS server on network adapter
ansible.windows.win_dns_client:
adapter_names: "*"
ipv4_addresses:
- "{{ dns_server }}"
when: ansible_os_family == 'Windows'
- name: Configure Windows Time Service
ansible.windows.win_shell: |
w32tm /config /manualpeerlist:"{{ ntp_server }}" /syncfromflags:manual /reliable:yes /update
when: ansible_os_family == 'Windows'
- name: Restart Windows Time Service
ansible.windows.win_service:
name: W32Time
state: restarted
when: ansible_os_family == 'Windows'
Again:
hosts: windows_groupuses the sameinventory.ymlas the Linux block.ansible_userandansible_passwordcome from the encrypted workspace configuration viablock.config, not in plain text from the inventory.ansible_os_family == 'Windows'guards OS-specific tasks if you later extend the group (e.g. with appliances).
Run the action with:
polycrate run windows-mgmt update-windows
Because execution runs in a container, dependencies such as pywinrm are already included. With plain Ansible you would need to ensure on every admin machine:
- a compatible Python version,
- Ansible and the
ansible.windowscollection installed, - WinRM configured and tested.
Polycrate removes that setup overhead – the strength of the container model with Ansible, as described in the Ansible integration.
Workflow That Ties It Together: Linux First, Then Windows
The benefit of a shared workspace shows in the workflow. We already defined hybrid-maintenance in workspace.poly:
workflows:
- name: hybrid-maintenance
steps:
- name: update-linux
block: linux-mgmt
action: update-linux
- name: update-windows
block: windows-mgmt
action: update-windows
Run a full maintenance cycle with:
polycrate workflows run hybrid-maintenance
What happens:
- Polycrate starts a container and runs the
update-linuxaction of blocklinux-mgmt. - After success, it starts the next container and runs
update-windowson blockwindows-mgmt. - Order is fixed in the workflow – no more guessing who patches what first.
You can extend workflows with pre-checks (e.g. free disk space), maintenance windows, or post-checks (e.g. service health). Workflows connect blocks and help avoid unstructured playbook sprawl. See Workflows.
Polycrate vs. Plain Ansible in a Hybrid Setup
Compared to a classic setup, with plain Ansible you would typically:
- Install Ansible locally on a Linux machine (watch Python versions).
- Install and maintain Windows extras (e.g. pywinrm).
- Maintain separate repos or folders for Windows playbooks and inventories.
- Manage secrets with Ansible Vault, an external secrets manager, or ad-hoc storage.
- Explain to colleagues which
ansible-playbookcommands to run in which order.
With Polycrate you get:
- Dependency isolation in the container: Ansible, Python, WinRM dependencies, and tools like
kubectlorhelmship in a defined toolchain – no workstation setup drift. - Sharable automation: Your
linux-mgmtandwindows-mgmtblocks can be shared via an OCI registry – internally or through PolyHub (reference scheme likeregistry.acme-corp.com/acme/infra/windows-mgmt:0.1.0; see PolyHub). - Guardrails from the block model: Playbooks gain structure via
block.poly,workspace.poly, and workflows (Best Practices). - Better UX: Colleagues remember
polycrate run windows-mgmt update-windowsorpolycrate workflows run hybrid-maintenanceinstead of raw Ansible CLI. - Built-in encryption: Secrets stay in the workspace and are stored encrypted – no extra vault product required.
Ansible remains the automation engine; Polycrate adds structure, reuse, and team focus.
Frequently Asked Questions
Can I add more operating systems to the same workspace?
Yes. The inventory can have any number of groups, and playbooks can branch on ansible_os_family or other facts. For macOS or appliances, add groups and either new blocks or more actions on existing blocks. Keep the pattern: clear blocks, clear inventory, clear workflows.
How does workspace encryption fit internal compliance (e.g. GDPR, ISO 27001)?
Polycrate workspace encryption uses age and encrypts sensitive files (SSH keys, passwords) in the repository. That gives you separation of config and plaintext secrets, traceable key rotation via Git history and CLI, and technical controls that map well to ISO 27001 and GDPR expectations (e.g. protecting access to production systems). Details: Workspace encryption.
How does ayedo help with hybrid workspaces?
We often work with teams that have grown Windows domains, new Linux workloads, and compliance requirements. In a hybrid workshop we shape:
- a workspace structure that fits your organization,
- Linux and Windows blocks (often from internal standards or existing Ansible playbooks),
- workflows for patch days, onboarding servers, or baseline hardening.
On request we integrate monitoring, CMDB, ticketing, and help you build your own block registry.
More questions? See our FAQ.
From Routine to Reproducibility
With the hybrid workspace in this article you gain three things:
- One view of the infrastructure: Linux and Windows servers share one inventory and one workspace.
- Structured automation: Two blocks (
linux-mgmt,windows-mgmt) wrap OS-specific playbooks; workflowhybrid-maintenancedefines the sequence. - Safe configuration: DNS/NTP and non-sensitive block parameters live in
workspace.poly(andsecrets.polyfor passwords); the SSH key as a file underartifacts/secrets/, WinRM password insecrets.poly– with encryption enabled you only commit.ageartifacts.
That turns the classic patch day – often a mix of manual steps, RDP, and ad-hoc scripts – into a reproducible, documented flow: a clear entry point (polycrate workflows run hybrid-maintenance), traceable changes (Git history plus Ansible logs), and reusable blocks for other workspaces or teams (internal registry or PolyHub).
At ayedo we help teams move from isolated scripts to reusable building blocks. Whether you come from Linux, Windows, or compliance – a shared hybrid workspace is a solid foundation for policies, hardening, monitoring, or self-service for other teams.
If you want to structure your hybrid infrastructure this way and learn from other environments, our Hybrid Infrastructure Workshop is a good starting point.