Artifacts

Workspace artifacts in Polycrate: the artifacts/ directory structure, block artifact directories, the secrets directory, standard artifacts such as inventory and kubeconfig, using and generating artifacts in actions, and the migration from Artifact Discovery to template blocks.

Artifacts

| Concept | Status | Where documented? |
|---|---|---|
| **Workspace artifacts** (files in the `artifacts/` folder per block during action execution) | **Active – full feature support continues** | **This page** |
| **Artifact Discovery / Embedded CRDs / ArtifactRepository** (CRs managed by the operator on a per-block basis) | **Removed in CLI 0.34.0** – replaced by [template blocks](/docs/polycrate/blocke/) in the [PolyHub](/docs/polycrate/polyhub/) | Migration note below |

If you landed on this page because you were looking for the **Artifact Discovery** feature: see the section [Migration to template blocks](#migration-artifact-discovery-template-blocks) at the end.

What are artifacts?

Artifacts are files that are generated, required or persisted during the execution of actions. They live per block under artifacts/blocks/<block>/ and are preserved between action runs; there is no shared artifact storage between different blocks.

Typical artifacts are:

  • Ansible inventories - dynamically generated or copied host lists
  • Kubeconfigs - Kubernetes cluster access configurations
  • Secrets - encrypted sensitive files (passwords, certificates, keys)
  • SSH keys - SSH key pairs for remote access
  • Generated manifests - YAML/JSON files for Kubernetes or other systems
  • Certificates - TLS certificates, CA bundles
  • Backup files - database dumps, configuration backups

Artifacts structure

Path Description Sensitive
inventory.yml Ansible inventory (workspace root) No
artifacts/ Artifacts root -
artifacts/secrets/ Workspace-wide secrets ⚠️ Yes
artifacts/secrets/id_rsa SSH private key ⚠️ Yes
artifacts/secrets/id_rsa.pub SSH public key No
artifacts/secrets/kubeconfig.yml Kubernetes config ⚠️ Yes
artifacts/secrets/<block>/ Block-specific secrets ⚠️ Yes
artifacts/blocks/ Block artifacts No
artifacts/blocks/<block>/generated/ Generated manifests No

artifacts/blocks/<block>/hosts_cache.json was formerly used as an SSH host cache; current Polycrate versions no longer create this file (existing workspaces may still contain it).

Default directory structure

my-workspace/
├── inventory.yml               # 📋 Ansible inventory (workspace root, YAML)
├── artifacts/                  # Artifacts root (configurable)
│   ├── secrets/                # 🔐 Encrypted secrets
│   │   ├── id_rsa              # Workspace SSH key (encrypted)
│   │   ├── id_rsa.pub          # Workspace SSH pubkey
│   │   ├── kubeconfig.yml      # Kubernetes kubeconfig (encrypted)
│   │   └── my-app/             # Block-specific secrets
│   │       ├── database.pwd    # Database password
│   │       └── api-key.txt     # API key
│   └── blocks/                 # Block artifacts
│       ├── my-cluster/
│       │   └── cluster-info.json
│       ├── infrastructure/
│       │   └── (block-specific artifacts)
│       └── my-app/
│           └── generated/
│               ├── deployment.yml
│               └── configmap.yml
├── secrets.poly.age            # 🔐 Encrypted secrets.poly
└── blocks/                     # Block definitions

Configuring the artifacts root

The artifacts root directory can be configured in workspace.poly. The YAML key is artifactsroot (one word, no underscore), not artifacts_root.

name: my-workspace

config:
  artifactsroot: artifacts  # Default
  # or a different path:
  # artifactsroot: .artifacts
  # artifactsroot: /tmp/polycrate-artifacts

Alternatively via the global CLI flag (sets workspace.config.artifactsroot):

polycrate --artifacts-root /custom/path run my-block install

Block artifacts directory

A block artifacts directory is created automatically for every block:

artifacts/blocks/<BLOCK_NAME>/

This directory:

  • Is created automatically when the block is first loaded
  • Is persistent between action executions
  • Is mounted into the container
  • Is part of the snapshot

Block artifact variables in Ansible/Jinja

The following snapshot variables are available in playbooks and Jinja templates:

Variable Description Example
block.artifacts.path Generic path artifacts/blocks/my-app
block.artifacts.localpath Local path (host) /abs/path/artifacts/blocks/my-app
block.artifacts.containerpath Path in the Ansible container /polycrate/artifacts/blocks/my-app
block.artifacts.secrets.path Path to the secrets folder artifacts/blocks/my-app/secrets
block.artifacts.secrets.localpath Local path to the secrets folder /abs/path/artifacts/blocks/my-app/secrets
block.artifacts.secrets.containerpath Container path to the secrets folder /polycrate/artifacts/blocks/my-app/secrets
```yaml
- name: Copy SSH key from block secrets
  copy:
    src: "{{ block.artifacts.secrets.localpath }}/id_rsa"
    dest: /tmp/deploy-key
    mode: '0600'
```

Secrets directory

The secrets directory (artifacts/secrets/) is a special area for sensitive files that are encrypted automatically during workspace encryption.

Structure

artifacts/secrets/
├── id_rsa              # Workspace SSH key
├── id_rsa.pub          # Workspace SSH pubkey
├── kubeconfig.yml      # Workspace kubeconfig
└── <block-name>/       # Block-specific secrets
    ├── database.pwd    # Password file
    ├── api-key.txt     # API key
    └── cert.pem        # Certificate

Workspace secrets vs. block secrets

Type Path Access in Ansible
Workspace secrets artifacts/secrets/ {{ workspace.secrets['filename'] }}
Block secrets artifacts/secrets/<block>/ {{ block.secrets['filename'] }}

Example: using secrets in Ansible

# deploy.yml
- name: Deploy with secrets
  hosts: all
  tasks:
    # Use workspace secret
    - name: Kubeconfig from workspace secrets
      shell: kubectl --kubeconfig={{ workspace.secrets['kubeconfig.yml'] }} get pods

    # Use block secret
    - name: Database password from block secrets
      shell: mysql -p$(cat {{ block.secrets['database.pwd'] }}) -h localhost

Encryption

All files in artifacts/secrets/ are encrypted automatically when you encrypt the workspace:

# Encrypt workspace (incl. secrets)
polycrate workspace encrypt

# Show status
polycrate workspace status

# Decrypt for editing
polycrate workspace decrypt

After encryption:

  • secrets.poly → secrets.poly.age
  • artifacts/secrets/id_rsa → artifacts/secrets/id_rsa.age
  • artifacts/secrets/kubeconfig.yml → artifacts/secrets/kubeconfig.yml.age

→ Detailed documentation on workspace encryption

Standard artifacts

1. Ansible inventory

The Ansible inventory lives as inventory.yml directly in the workspace root. It must be in YAML format — INI format is not supported. Polycrate sets ANSIBLE_INVENTORY to this path automatically.

# inventory.yml  (workspace root)
all:
  hosts:
    web1:
      ansible_host: 192.168.1.10
      ansible_user: deploy
      ansible_port: 22
    web2:
      ansible_host: 192.168.1.11
      ansible_user: deploy
  vars:
    ansible_python_interpreter: /usr/bin/python3
  children:
    webservers:
      hosts:
        web1: {}
        web2: {}

2. Kubeconfig

The kubeconfig must be stored under artifacts/secrets/kubeconfig.yml. This path is inside the secrets directory and is therefore automatically covered by workspace encryption.

# artifacts/secrets/kubeconfig.yml
apiVersion: v1
kind: Config
clusters:
  - name: prod-cluster
    cluster:
      server: https://k8s.example.com:6443
      certificate-authority-data: LS0tLS...
contexts:
  - name: prod
    context:
      cluster: prod-cluster
      user: admin
      namespace: production
current-context: prod
users:
  - name: admin
    user:
      client-certificate-data: LS0tLS...
      client-key-data: LS0tLS...

3. SSH host cache (historical)

Earlier Polycrate versions could cache SSH connection data under artifacts/blocks/<block>/hosts_cache.json. Current releases no longer create this file. The file may still exist in older workspaces; it is no longer actively managed.

Using artifacts in actions

In Ansible playbooks

Artifacts are automatically available via snapshot variables:

---
- name: Deploy Application
  hosts: localhost
  connection: local

  tasks:
    - name: Load generated manifest
      set_fact:
        manifest: "{{ lookup('file', block.artifacts.path + '/generated/deployment.yml') }}"

    - name: Apply Manifest
      kubernetes.core.k8s:
        state: present
        definition: "{{ manifest }}"

Best practice: Ansible playbooks

All actions should be implemented with Ansible playbooks:

actions:
  - name: status
    playbook: status.yml

  - name: deploy
    playbook: deploy.yml

In the playbook you have access to all workspace and block variables via Jinja2 templating.

Generating artifacts

Example 1: generating a kubeconfig

Complex artifact generation should happen in an Ansible playbook:

# block.poly
actions:
  - name: create-cluster
    playbook: create-cluster.yml

  - name: get-kubeconfig
    playbook: get-kubeconfig.yml
# get-kubeconfig.yml
- name: Fetch and store kubeconfig
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Export kubeconfig
      shell: kubectl config view --flatten
      register: kubeconfig_output

    - name: Store kubeconfig as artifact
      copy:
        content: "{{ kubeconfig_output.stdout }}"
        dest: "{{ block.artifacts.path }}/kubeconfig.yml"
        mode: '0600'

    - name: Confirmation
      debug:
        msg: "Kubeconfig saved to {{ block.artifacts.path }}/kubeconfig.yml"

The workspace kubeconfig for Kubernetes actions, by contrast, lives at the fixed location artifacts/secrets/kubeconfig.yml (see section Kubeconfig). Another block entry in workspace.poly can refer to the kubeconfig context of another block via kubeconfig.from: – this is configuration wiring, not a shared artifact directory:

# workspace.poly
blocks:
  - name: my-cluster
    from: registry.my-org.com/blocks/k8s/cluster:1.0.0

  - name: my-app
    from: registry.my-org.com/blocks/k8s/my-app:1.0.0
    kubeconfig:
      from: my-cluster  # Uses kubeconfig.yml of block "my-cluster"

Example 2: generating an inventory from a cloud API

# generate-inventory.yml
- name: Generate inventory from cloud API
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Fetch cloud instances
      amazon.aws.ec2_instance_info:
        region: "{{ block.config.region }}"
        filters:
          "tag:Environment": "{{ block.config.environment }}"
      register: ec2_instances

    - name: Generate inventory
      template:
        src: inventory.yml.j2
        dest: "{{ block.artifacts.path }}/inventory.yml"
      vars:
        hosts: "{{ ec2_instances.instances }}"

Example 3: persisting generated configuration

# generate-config.yml
- name: Generate and persist configuration
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Generate configuration
      template:
        src: config.yml.j2
        dest: "{{ block.artifacts.path }}/config.yml"
      vars:
        namespace: "{{ block.config.namespace }}"
        replicas: "{{ block.config.replicas }}"

    - name: Create cache file
      copy:
        content: "{{ block.config | to_nice_json }}"
        dest: "{{ block.artifacts.path }}/config_cache.json"

No artifact sharing between blocks

Artifacts are stored per block under artifacts/blocks/<block-name>/. There is no shared artifact repository: a block cannot directly read or "take over" the artifact files of another block, and workflows do not automatically pass artifact files between steps or blocks either.

What is possible in workspace.poly (e.g. kubeconfig.from:, inventory.from:) are references to other block instances for the workspace configuration (which context, which inventory applies) – not the simultaneous use of a shared artifact folder. Data that one block needs for the next must be provided via workspace-wide paths (e.g. inventory.yml, artifacts/secrets/) or by regenerating it in the target block.

Best practices

1. Artifacts for reproducible builds

Store all generated files as artifacts (in a playbook):

# build.yml
- name: Build and store artifacts
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Run build
      command: make build

    - name: Create artifacts directory
      file:
        path: "{{ block.artifacts.path }}/build"
        state: directory

    - name: Copy build artifacts
      copy:
        src: "{{ item }}"
        dest: "{{ block.artifacts.path }}/build/"
      loop:
        - dist/
        - build.log

2. Git and artifacts

artifacts/ contains reproducible outputs (manifests, generated inventories, block artifacts). These are part of the workspace repository and should usually be versioned so that GitOps and review processes remain traceable.

Volatile or large intermediate states (npm/pip caches, temporary downloads) do not belong under artifacts/ in the workspace: put such paths under /tmp in the container or outside of workspace.path, or clean them up in an action.

Secrets: do not commit unencrypted secrets – see Workspace encryption.

3. Artifacts cleanup

Implement cleanup actions as a playbook:

# clean.yml
- name: Clean up artifacts
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Empty artifacts directory
      file:
        path: "{{ block.artifacts.path }}"
        state: absent

    - name: Recreate artifacts directory
      file:
        path: "{{ block.artifacts.path }}"
        state: directory

    - name: Confirmation
      debug:
        msg: "Artifacts cleaned: {{ block.artifacts.path }}"

4. Document artifacts

Document which artifacts a block creates (in the block README):

# my-block

## Generated artifacts

| File | Description |
|-------|-------------|
| `kubeconfig.yml` | Kubernetes cluster access |
| `inventory.yml` | Ansible host list |
| `certs/ca.crt` | Root CA |
| `certs/tls.crt` | Server certificate |
| `certs/tls.key` | Private key (encrypted) |

5. Validate artifacts

Validate artifacts after generation:

# generate-inventory.yml
- name: Generate and validate inventory
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Generate inventory
      command: python3 scripts/generate-inventory.py
      register: inventory_output

    - name: Store inventory
      copy:
        content: "{{ inventory_output.stdout }}"
        dest: "{{ block.artifacts.path }}/inventory.yml"

    - name: Validate inventory
      command: ansible-inventory -i {{ block.artifacts.path }}/inventory.yml --list
      register: validation

    - name: Validation successful
      debug:
        msg: "Inventory validated: {{ (validation.stdout | from_json).all.hosts | length }} hosts"

Artifacts in the lifecycle

sequenceDiagram
    participant User
    participant CLI
    participant Block
    participant Artifacts

    User->>CLI: polycrate run block provision
    CLI->>Artifacts: Load existing artifacts
    Artifacts-->>CLI: inventory.yml, kubeconfig.yml
    CLI->>Block: Execute action
    Block->>Artifacts: Generate new artifacts
    Artifacts-->>Block: Stored
    Block-->>CLI: Action successful
    CLI->>Artifacts: Workspace reload: load new artifacts
    Artifacts-->>CLI: Updated artifacts
    CLI-->>User: Done

    User->>CLI: polycrate run app deploy
    CLI->>Artifacts: Load artifacts (incl. newly generated)
    Artifacts-->>CLI: inventory.yml, kubeconfig.yml
    CLI->>Block: Execute action with artifacts
  1. Before the action: existing artifacts are loaded
  2. During the action: new artifacts are generated/modified
  3. After the action: the workspace is reloaded (new artifacts available)
  4. Next action: uses the updated artifacts

Relation to other concepts

  • Snapshot: artifacts are part of the snapshot
  • Inheritance: block artifacts and configurations can be inherited
  • Workflows: workflows execute actions in a defined order (no automatic artifact sharing between blocks)
  • Container: artifacts are mounted into the container

Migration: Artifact Discovery → Template Blocks

The second, discontinued artifact concept was Artifact Discovery / ArtifactRepository / Embedded CRDs – a mechanism in which blocks additionally shipped Kubernetes CRs ("embedded artifacts") that the Polycrate Operator collected via an ArtifactRepository and an ArtifactDiscovery controller and applied to clusters.

Status

  • Removed in Polycrate CLI 0.34.0 (see release notes)
  • Removed in Polycrate API 0.15.0 – the operator no longer knows ArtifactRepository / ArtifactDiscovery
  • Data model ArtifactRepository and the associated Celery tasks have been completely removed from the API

Why was it replaced?

Problem Solution with template blocks
Embedded CRDs were invisible (hidden in block source files) Template blocks are standalone, versioned objects in the PolyHub
No clean versioning of individual artifacts possible Every template block has its own versions and changelogs
The operator had to clone blocks just to read CRs The operator installs template blocks directly from the Polycrate API
No UI for browsing The PolyHub offers a full browsing UI, filters, readmes, changelog

Migration path

  1. Inventory: which embedded artifacts were shipped in existing blocks?
  2. Split: every embedded artifact becomes its own template block in the PolyHub – typically under the appropriate namespace (ayedo/k8s/<name>).
  3. Block release: template block with its own block.poly, README.md, CHANGELOG.poly and manifests → push to the PolyHub via polycrate blocks push.
  4. Polycrate API: create the PolyHub as a DataSource of kind polycrate-hub – the template blocks then appear in Polycrate automatically.
  5. Operator: cluster/app installs template blocks directly from the API in future (instead of from an ArtifactRepository).
  6. Slim down the old block: remove the artifacts/ sections with CRs from the old block; only the actual install logic remains.

See also