Skip to content

How to run Ansible in CI/CD

How-to · Updated May 2026

Coming from another cloud?

▸AWS·Codepipeline With Ansible

This Quake AI feature maps to AWS’s Codepipeline With Ansible.

▸Azure·Pipelines With Ansible

This Quake AI feature maps to Azure’s Pipelines With Ansible.

Before this

How to run Ansible in CI/CD

This guide runs Ansible playbooks against Quake AI from GitHub Actions and GitLab CI. The same pipeline patterns drive scheduled patching, release deploys, and the Day 1 configuration step that follows an OpenTofu provision.

For the OpenTofu equivalent, see How to integrate OpenTofu with CI/CD. For the combined provision-and-configure flow, see How to use Ansible with OpenTofu on Quake AI.

Prerequisites#

  • A working playbook that runs locally against Quake AI (see getting-started)
  • Application credentials available as CI secrets, either as clouds.yaml content or as the discrete OS_* variables. The same credentials drive the modules and the dynamic inventory plugin.
  • A GitHub or GitLab repository with the playbook and inventory committed

Secrets configuration#

Both CI platforms need the Quake AI authentication values stored as secrets. The minimum set:

VariablePurpose
OS_AUTH_URLQuake AI Keystone endpoint
OS_APPLICATION_CREDENTIAL_IDApplication credential id
OS_APPLICATION_CREDENTIAL_SECRETApplication credential secret
OS_REGION_NAMERegion (for example us-east-1)
OS_INTERFACEpublic for most projects
OS_IDENTITY_API_VERSION3
ANSIBLE_VAULT_PASSWORDOptional: only if your playbook reads ansible-vault files
SSH_PRIVATE_KEYThe key Ansible uses to reach managed hosts (PEM format)

The OS_* variables shown above match the authentication chain the modules and the inventory plugin both read. CI runners typically prefer environment variables over a checked-in clouds.yaml.

GitHub actions#

Single-job playbook run#

The minimal pipeline installs Ansible plus openstacksdk, writes the SSH key to disk, and runs the playbook. Trigger on workflow_dispatch so production runs require an explicit click:

YAML
name: Configure infrastructure

on:
  workflow_dispatch:
  push:
    branches: [main]
    paths:
      - "playbooks/**"
      - "inventory/**"

jobs:
  configure:
    runs-on: ubuntu-latest
    env:
      OS_AUTH_URL: ${{ secrets.OS_AUTH_URL }}
      OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }}
      OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }}
      OS_REGION_NAME: ${{ secrets.OS_REGION_NAME }}
      OS_INTERFACE: public
      OS_IDENTITY_API_VERSION: "3"
      ANSIBLE_HOST_KEY_CHECKING: "False"
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install Ansible and openstacksdk
        run: |
          pip install "ansible-core>=2.13" "openstacksdk>=1.0.0"
          ansible-galaxy collection install openstack.cloud

      - name: Write SSH key
        run: |
          mkdir -p ~/.ssh
          chmod 700 ~/.ssh
          printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519

      - name: Run playbook
        run: |
          ansible-playbook \
            -i inventory/openstack.yml \
            --private-key ~/.ssh/id_ed25519 \
            playbooks/site.yml

ANSIBLE_HOST_KEY_CHECKING: "False" disables strict host key prompts that would otherwise hang in a non-interactive runner. For environments where strict checking is required, prepopulate ~/.ssh/known_hosts from a checked-in fixture instead.

Sequenced provision-then-configure#

The canonical Day 0 plus Day 1 pattern runs OpenTofu in one job and Ansible in a downstream job. The configure job depends on needs: provision, which guarantees order:

YAML
name: Deploy

on:
  workflow_dispatch:

jobs:
  provision:
    runs-on: ubuntu-latest
    env:
      OS_AUTH_URL: ${{ secrets.OS_AUTH_URL }}
      OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }}
      OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }}
      OS_REGION_NAME: ${{ secrets.OS_REGION_NAME }}
    steps:
      - uses: actions/checkout@v4
      - uses: opentofu/setup-opentofu@v1
      - run: tofu -chdir=infra init
      - run: tofu -chdir=infra apply -auto-approve

  configure:
    needs: provision
    runs-on: ubuntu-latest
    env:
      OS_AUTH_URL: ${{ secrets.OS_AUTH_URL }}
      OS_APPLICATION_CREDENTIAL_ID: ${{ secrets.OS_APPLICATION_CREDENTIAL_ID }}
      OS_APPLICATION_CREDENTIAL_SECRET: ${{ secrets.OS_APPLICATION_CREDENTIAL_SECRET }}
      OS_REGION_NAME: ${{ secrets.OS_REGION_NAME }}
      ANSIBLE_HOST_KEY_CHECKING: "False"
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11" }
      - run: |
          pip install "ansible-core>=2.13" "openstacksdk>=1.0.0"
          ansible-galaxy collection install openstack.cloud
      - name: Write SSH key
        run: |
          mkdir -p ~/.ssh && chmod 700 ~/.ssh
          printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
      - run: |
          ansible-playbook \
            -i inventory/openstack.yml \
            --private-key ~/.ssh/id_ed25519 \
            playbooks/site.yml

The configure job uses the dynamic inventory plugin, which queries Quake AI directly. The provision job does not need to pass an inventory file forward: Ansible discovers the new instance through the metadata OpenTofu set on it. The full handoff (including the alternative tofu output -json path) is documented in How to use Ansible with OpenTofu on Quake AI.

GitLab CI#

GitLab pipelines fit the same shape. Use a Python or Ansible image, pull credentials from masked CI/CD variables, and gate production with when: manual:

YAML
stages:
  - configure

variables:
  ANSIBLE_HOST_KEY_CHECKING: "False"
  OS_INTERFACE: public
  OS_IDENTITY_API_VERSION: "3"

.ansible_base:
  image: python:3.11-slim
  before_script:
    - apt-get update && apt-get install -y --no-install-recommends openssh-client
    - pip install "ansible-core>=2.13" "openstacksdk>=1.0.0"
    - ansible-galaxy collection install openstack.cloud
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519

configure_staging:
  extends: .ansible_base
  stage: configure
  script:
    - ansible-playbook -i inventory/openstack.yml --private-key ~/.ssh/id_ed25519 playbooks/site.yml --limit env_staging
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

configure_production:
  extends: .ansible_base
  stage: configure
  script:
    - ansible-playbook -i inventory/openstack.yml --private-key ~/.ssh/id_ed25519 playbooks/site.yml --limit env_production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  environment:
    name: production

In GitLab, mark the secret variables (OS_*, SSH_PRIVATE_KEY) as Masked and Protected so they never appear in logs and only run on protected branches. Configure them under Settings > CI/CD > Variables in the project.

If you prefer a pre-baked Ansible runtime, swap python:3.11-slim for quay.io/ansible/awx-ee or another community Ansible image. The trade-off is image size against having ansible-core and openstacksdk already installed.

Secret management#

A few rules keep the pipeline credible:

  • Never commit vault passwords, application credential secrets, or SSH private keys to the repository. Pipelines load them from the platform's secrets store.
  • If a playbook reads ansible-vault-encrypted files, supply the password through --vault-password-file <(echo "$ANSIBLE_VAULT_PASSWORD") rather than echoing the value into a file the runner does not clean up.
  • Rotate the application credential and the SSH private key on a schedule (every 90 days is a common cadence). Generate new ones, replace the secret values, and retire the old credential.
  • Scope each pipeline to its own application credential. A leaked secret in a staging pipeline should not have permission to apply changes in production.
  • Restrict who can trigger workflow_dispatch runs and who can approve manual GitLab gates. The platform's secrets store is only as strong as the access control on the people who can call it.

Tear down#

The pipeline configurations themselves do not create persistent infrastructure. Removing them stops future runs but does not change anything on Quake AI. To roll back the configuration applied by the playbook, write a teardown playbook with state: absent tasks (the provisioning guide shows the pattern) and run it through the same pipeline.

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 23.05.2026

Quick answers

Was this page helpful?