19 Apr 2025, Sat

In today’s rapidly evolving IT landscape, automation has become essential for managing infrastructure efficiently. Two powerful tools that have gained significant popularity are Pulumi and Ansible. While both fall under the broader category of infrastructure automation, they approach the problem from different angles and excel in different scenarios. Understanding these differences is crucial for selecting the right tool for your specific needs.

This comprehensive guide will explore the key characteristics, strengths, and ideal use cases for both Pulumi and Ansible, helping you make an informed decision about which tool to implement in your environment.

Understanding the Core Approaches

Before comparing specific features, it’s important to understand the fundamental philosophy and approach of each tool.

Pulumi: Infrastructure as Code with Programming Languages

Pulumi represents a modern approach to infrastructure as code (IaC) by allowing engineers to define cloud resources using familiar programming languages like Python, TypeScript/JavaScript, Go, C#, and Java. This is in contrast to traditional IaC tools that typically use domain-specific languages or templating formats.

# Python example of Pulumi creating an AWS S3 bucket
import pulumi
import pulumi_aws as aws

# Create an AWS S3 bucket
bucket = aws.s3.Bucket("my-data-lake",
    acl="private",
    versioning=aws.s3.BucketVersioningArgs(
        enabled=True,
    ),
    tags={
        "Environment": "Production",
        "Department": "Data Engineering",
    }
)

# Export the bucket name
pulumi.export("bucket_name", bucket.id)

Ansible: Declarative Configuration Management and Orchestration

Ansible takes a different approach, focusing on configuration management and application deployment through declarative YAML files called “playbooks.” It operates in an agentless manner, typically connecting to managed nodes via SSH to execute tasks.

# Ansible playbook example to configure a web server
---
- name: Configure web servers
  hosts: webservers
  become: yes
  
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        
    - name: Configure Nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify:
        - Restart Nginx
        
    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
        
  handlers:
    - name: Restart Nginx
      service:
        name: nginx
        state: restarted

Key Differentiating Factors

Now let’s examine the critical differences between these tools to help you understand when each is most appropriate.

1. Infrastructure Provisioning vs. Configuration Management

Pulumi excels at:

  • Creating and managing cloud resources (VMs, networks, databases)
  • Defining entire infrastructure stacks declaratively
  • Managing the lifecycle of cloud infrastructure

Ansible excels at:

  • Configuring operating systems and applications
  • Deploying and updating software
  • Managing service configurations

2. Programming Model and Expressiveness

Pulumi offers:

  • Full-featured programming languages (loops, conditionals, functions)
  • Object-oriented abstractions and strong typing
  • Ability to use existing libraries and package managers

Ansible provides:

  • YAML-based declarative syntax
  • Jinja2 templating for basic logic
  • Module-based approach for reusability

3. State Management

Pulumi implements:

  • Explicit state tracking of created resources
  • State stored in the Pulumi service or self-hosted backend
  • Dependency graph management for proper creation/deletion order

Ansible operates:

  • Largely stateless (idempotent operations)
  • Optional inventory for tracking managed nodes
  • Typically no persistent record of applied configurations

4. Execution Model

Pulumi uses:

  • Local CLI or CI/CD for execution
  • Provider plugins for API communication
  • Plan/apply workflow similar to Terraform

Ansible employs:

  • Agentless execution over SSH/WinRM
  • Push-based configuration from control node to targets
  • Linear task execution with dependency management

When to Choose Pulumi

Pulumi is the better choice in these specific scenarios:

1. Cloud-Native Infrastructure Management

If your primary goal is managing cloud resources across providers like AWS, Azure, Google Cloud, or Kubernetes, Pulumi offers powerful capabilities.

Example: Multi-Cloud Data Platform

// TypeScript example of a multi-cloud data platform with Pulumi
import * as aws from "@pulumi/aws";
import * as gcp from "@pulumi/gcp";
import * as k8s from "@pulumi/kubernetes";

// AWS S3 bucket for data lake storage
const dataBucket = new aws.s3.Bucket("data-lake", {
    versioning: {
        enabled: true,
    },
    serverSideEncryptionConfiguration: {
        rule: {
            applyServerSideEncryptionByDefault: {
                sseAlgorithm: "AES256",
            },
        },
    },
});

// GCP BigQuery dataset for analytics
const dataset = new gcp.bigquery.Dataset("analytics", {
    datasetId: "enterprise_analytics",
    location: "US",
    defaultTableExpirationMs: 7776000000, // 90 days
});

// Kubernetes deployment for data processing application
const appLabels = { app: "data-processor" };
const deployment = new k8s.apps.v1.Deployment("data-processor", {
    spec: {
        selector: { matchLabels: appLabels },
        replicas: 3,
        template: {
            metadata: { labels: appLabels },
            spec: {
                containers: [{
                    name: "data-processor",
                    image: "data-processor:v1.0.0",
                    resources: {
                        requests: {
                            cpu: "500m",
                            memory: "512Mi",
                        },
                        limits: {
                            cpu: "1000m",
                            memory: "1024Mi",
                        },
                    },
                    env: [
                        { name: "S3_BUCKET", value: dataBucket.bucket },
                        { name: "BIGQUERY_DATASET", value: dataset.datasetId },
                    ],
                }],
            },
        },
    },
});

// Export important resource identifiers
export const bucketName = dataBucket.id;
export const datasetId = dataset.datasetId;
export const deploymentName = deployment.metadata.name;

This example demonstrates how Pulumi can seamlessly manage resources across multiple cloud providers with a single, cohesive program.

2. Complex Infrastructure Logic Requirements

When your infrastructure has complex dependencies or requires sophisticated logic, Pulumi’s programming model shines.

Example: Dynamic Resource Allocation

# Python example of dynamic resource allocation based on environment
import pulumi
import pulumi_aws as aws

config = pulumi.Config()
environment = config.require("environment")

# Define environment-specific settings
env_settings = {
    "dev": {
        "instance_type": "t3.small",
        "instance_count": 2,
        "multi_az": False
    },
    "staging": {
        "instance_type": "t3.medium",
        "instance_count": 2,
        "multi_az": True
    },
    "production": {
        "instance_type": "m5.large",
        "instance_count": 5,
        "multi_az": True
    }
}

# Use the appropriate settings based on environment
settings = env_settings.get(environment, env_settings["dev"])

# Create a security group
security_group = aws.ec2.SecurityGroup(f"{environment}-app-sg",
    description=f"Security group for {environment} application servers",
    ingress=[
        # Allow HTTP and HTTPS from anywhere
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=80,
            to_port=80,
            cidr_blocks=["0.0.0.0/0"],
        ),
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=443,
            to_port=443,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ],
)

# Create a Launch Configuration
launch_configuration = aws.ec2.LaunchConfiguration(f"{environment}-app-lc",
    image_id="ami-0c55b159cbfafe1f0",
    instance_type=settings["instance_type"],
    security_groups=[security_group.id],
    user_data="""#!/bin/bash
echo "Hello from Pulumi" > index.html
nohup python -m SimpleHTTPServer 80 &
""",
)

# Create an Auto Scaling Group
auto_scaling_group = aws.autoscaling.Group(f"{environment}-app-asg",
    launch_configuration=launch_configuration.id,
    availability_zones=["us-east-1a", "us-east-1b", "us-east-1c"],
    min_size=settings["instance_count"],
    max_size=settings["instance_count"] * 2,
    tags=[
        aws.autoscaling.GroupTagArgs(
            key="Environment",
            value=environment,
            propagate_at_launch=True,
        ),
    ],
)

# Create a database instance
database = aws.rds.Instance(f"{environment}-database",
    engine="mysql",
    instance_class=f"db.{settings['instance_type']}",
    allocated_storage=20,
    multi_az=settings["multi_az"],
    username="admin",
    password=config.require_secret("db_password"),
    skip_final_snapshot=True,
    tags={
        "Environment": environment,
    },
)

# Export important resource information
pulumi.export("asg_name", auto_scaling_group.name)
pulumi.export("database_endpoint", database.endpoint)

This example demonstrates how Pulumi leverages Python’s programming constructs to dynamically allocate resources based on environment-specific requirements.

3. Developer-Focused Teams

For teams primarily composed of software developers who prefer to work with familiar programming languages, Pulumi offers a more natural fit.

Example: Infrastructure Unit Testing

// TypeScript example of testing infrastructure with Pulumi
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import { InfrastructureStack } from "../src/infrastructure";
import * as assert from "assert";

pulumi.runtime.setMocks({
    newResource: function(args: pulumi.runtime.MockResourceArgs): {id: string, state: any} {
        return {
            id: args.inputs.name + "_id",
            state: args.inputs,
        };
    },
    call: function(args: pulumi.runtime.MockCallArgs) {
        return args.inputs;
    },
});

describe("Infrastructure", function() {
    const infra = new InfrastructureStack("test");
    
    it("creates a VPC with the correct CIDR", function(done) {
        pulumi.all([infra.vpc.cidrBlock]).apply(([cidr]) => {
            assert.strictEqual(cidr, "10.0.0.0/16");
            done();
        });
    });
    
    it("creates private subnets in all availability zones", function(done) {
        pulumi.all([infra.privateSubnets]).apply(([subnets]) => {
            assert.strictEqual(subnets.length, 3);
            done();
        });
    });
    
    it("configures RDS with high availability in production", function(done) {
        pulumi.all([infra.database.multiAz]).apply(([multiAz]) => {
            assert.strictEqual(multiAz, true);
            done();
        });
    });
});

This example shows how infrastructure defined with Pulumi can be tested using standard software testing frameworks.

When to Choose Ansible

Ansible is particularly well-suited for these scenarios:

1. Server Configuration and Application Deployment

If your primary focus is configuring servers, installing software, and deploying applications, Ansible’s approach is often more straightforward.

Example: Comprehensive Web Application Deployment

---
# Ansible playbook for deploying a web application
- name: Configure web application servers
  hosts: web_servers
  become: yes
  vars:
    app_name: my_web_app
    app_version: 1.2.3
    db_host: db.example.com
    app_env: production
    
  tasks:
    # Update system packages
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600
        
    # Install required packages
    - name: Install dependencies
      apt:
        name:
          - nginx
          - python3
          - python3-pip
          - python3-venv
          - supervisor
        state: present
        
    # Create application user
    - name: Create application user
      user:
        name: "{{ app_name }}"
        comment: "Application User"
        shell: /bin/bash
        
    # Set up application directory
    - name: Create application directories
      file:
        path: "/opt/{{ app_name }}/{{ item }}"
        state: directory
        owner: "{{ app_name }}"
        group: "{{ app_name }}"
        mode: '0755'
      loop:
        - releases
        - shared
        - shared/logs
        - shared/config
        
    # Download application release
    - name: Download application release
      get_url:
        url: "https://artifacts.example.com/{{ app_name }}/{{ app_name }}-{{ app_version }}.tar.gz"
        dest: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
        
    # Extract application
    - name: Extract application
      unarchive:
        src: "/tmp/{{ app_name }}-{{ app_version }}.tar.gz"
        dest: "/opt/{{ app_name }}/releases/"
        remote_src: yes
        creates: "/opt/{{ app_name }}/releases/{{ app_version }}"
        owner: "{{ app_name }}"
        group: "{{ app_name }}"
        
    # Update current symlink
    - name: Update current symlink
      file:
        src: "/opt/{{ app_name }}/releases/{{ app_version }}"
        dest: "/opt/{{ app_name }}/current"
        state: link
        
    # Create virtual environment
    - name: Set up Python virtualenv
      pip:
        requirements: "/opt/{{ app_name }}/current/requirements.txt"
        virtualenv: "/opt/{{ app_name }}/shared/venv"
        virtualenv_command: python3 -m venv
        
    # Configure application
    - name: Create application config
      template:
        src: app_config.j2
        dest: "/opt/{{ app_name }}/shared/config/config.py"
        owner: "{{ app_name }}"
        group: "{{ app_name }}"
        mode: '0644'
        
    # Set up Nginx configuration
    - name: Configure Nginx
      template:
        src: nginx_site.j2
        dest: "/etc/nginx/sites-available/{{ app_name }}"
      notify:
        - Reload Nginx
        
    - name: Enable Nginx site
      file:
        src: "/etc/nginx/sites-available/{{ app_name }}"
        dest: "/etc/nginx/sites-enabled/{{ app_name }}"
        state: link
      notify:
        - Reload Nginx
        
    # Set up Supervisor for the application
    - name: Configure Supervisor for application
      template:
        src: supervisor_app.j2
        dest: "/etc/supervisor/conf.d/{{ app_name }}.conf"
      notify:
        - Reload Supervisor
        
    # Start services
    - name: Ensure services are running
      service:
        name: "{{ item }}"
        state: started
        enabled: yes
      loop:
        - nginx
        - supervisor
        
  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded
        
    - name: Reload Supervisor
      supervisorctl:
        name: "{{ app_name }}"
        state: restarted

This comprehensive example shows how Ansible can handle the complete process of configuring servers and deploying a web application.

2. Mixed Infrastructure with Legacy Systems

When dealing with a mix of infrastructure including legacy systems, on-premises equipment, and various operating systems, Ansible’s agentless approach offers great flexibility.

Example: Managing Heterogeneous Infrastructure

---
# Inventory file example

[webservers]

web1.example.com web2.example.com

[dbservers]

db1.example.com ansible_ssh_user=dbadmin db2.example.com ansible_ssh_user=dbadmin

[windows]

win1.example.com ansible_connection=winrm ansible_user=administrator win2.example.com ansible_connection=winrm ansible_user=administrator

[network:children]

cisco juniper

[cisco]

switch1.example.com ansible_network_os=ios router1.example.com ansible_network_os=ios

[juniper]

switch2.example.com ansible_network_os=junos # Main playbook — – name: Configure Linux web servers hosts: webservers roles: – common – webserver – name: Configure database servers hosts: dbservers roles: – common – database – name: Configure Windows servers hosts: windows roles: – windows_common – windows_app – name: Backup network device configurations hosts: network gather_facts: no tasks: – name: Backup configuration network_cli: backup: yes register: config_backup – name: Save backup details copy: content: “{{ config_backup | to_nice_yaml }}” dest: “backups/{{ inventory_hostname }}_config.yml” delegate_to: localhost

This example demonstrates Ansible’s ability to manage diverse infrastructure types from a single inventory and playbook structure.

3. Operational Tasks and Ad-hoc Changes

For day-to-day operational tasks, maintenance activities, and ad-hoc changes, Ansible provides a straightforward approach with minimal overhead.

Example: Maintenance Operations

# Patch management playbook
---
- name: System patching and maintenance
  hosts: all
  become: yes
  
  tasks:
    # Create a backup snapshot on cloud instances
    - name: Create pre-update snapshot
      ec2_snapshot:
        instance_id: "{{ ec2_id }}"
        device_name: /dev/sda1
        description: "Pre-update snapshot - {{ ansible_date_time.date }}"
      when: cloud_provider == "aws"
      delegate_to: localhost
      
    # Update package cache
    - name: Update apt cache
      apt:
        update_cache: yes
      when: ansible_os_family == "Debian"
      
    - name: Update yum cache
      yum:
        update_cache: yes
      when: ansible_os_family == "RedHat"
      
    # Apply updates
    - name: Apply updates
      package:
        name: "*"
        state: latest
      register: update_result
      
    # Reboot if needed
    - name: Check if reboot is required
      stat:
        path: /var/run/reboot-required
      register: reboot_required
      when: ansible_os_family == "Debian"
      
    - name: Reboot if required (Debian/Ubuntu)
      reboot:
        msg: "Reboot by Ansible due to package updates"
        connect_timeout: 5
        reboot_timeout: 300
        pre_reboot_delay: 0
        post_reboot_delay: 30
      when: ansible_os_family == "Debian" and reboot_required.stat.exists
      
    - name: Reboot if kernel updated (RedHat)
      reboot:
        msg: "Reboot by Ansible due to kernel update"
        connect_timeout: 5
        reboot_timeout: 300
        pre_reboot_delay: 0
        post_reboot_delay: 30
      when: ansible_os_family == "RedHat" and update_result.changed

This playbook demonstrates how Ansible can perform system maintenance tasks across multiple systems efficiently.

4. Organizations with Limited Programming Expertise

For teams with system administrators who may have limited programming experience, Ansible’s YAML-based approach is often more accessible.

Example: Self-Service Operations

# Simple operations playbook for non-developers
---
- name: Application management tasks
  hosts: app_servers
  become: yes
  vars_prompt:
    - name: operation
      prompt: "Select operation (restart_app, deploy_version, rollback, check_status)"
      private: no
      
    - name: app_version
      prompt: "Enter application version to deploy (only for deploy_version)"
      private: no
      default: "latest"
  
  tasks:
    # Restart application
    - name: Restart application
      service:
        name: myapp
        state: restarted
      when: operation == "restart_app"
      
    # Deploy specific version
    - name: Deploy specific version
      block:
        - name: Download application package
          get_url:
            url: "https://repo.example.com/myapp-{{ app_version }}.zip"
            dest: "/tmp/myapp-{{ app_version }}.zip"
            
        - name: Extract application
          unarchive:
            src: "/tmp/myapp-{{ app_version }}.zip"
            dest: "/opt/myapp"
            remote_src: yes
            
        - name: Restart application
          service:
            name: myapp
            state: restarted
      when: operation == "deploy_version"
      
    # Rollback to previous version
    - name: Rollback to previous version
      block:
        - name: Find previous version
          shell: "ls -1t /opt/myapp/backups/ | head -1"
          register: previous_version
          
        - name: Restore previous version
          copy:
            src: "/opt/myapp/backups/{{ previous_version.stdout }}"
            dest: "/opt/myapp/current"
            remote_src: yes
            
        - name: Restart application
          service:
            name: myapp
            state: restarted
      when: operation == "rollback"
      
    # Check application status
    - name: Check application status
      command: "systemctl status myapp"
      register: app_status
      when: operation == "check_status"
      
    - name: Display application status
      debug:
        msg: "{{ app_status.stdout_lines }}"
      when: operation == "check_status"

This example shows how Ansible can provide a simple interface for common operational tasks that even non-developers can use.

Combining Pulumi and Ansible for Complete Coverage

Many organizations find value in using both tools together, leveraging their respective strengths:

Example: Hybrid Approach for a Complete Application Stack

# Pulumi program for infrastructure provisioning (infrastructure.py)
import pulumi
import pulumi_aws as aws

# Create VPC and networking components
vpc = aws.ec2.Vpc("app-vpc",
    cidr_block="10.0.0.0/16",
    enable_dns_hostnames=True,
    enable_dns_support=True,
)

public_subnet = aws.ec2.Subnet("public-subnet",
    vpc_id=vpc.id,
    cidr_block="10.0.1.0/24",
    map_public_ip_on_launch=True,
)

# Create security group
app_security_group = aws.ec2.SecurityGroup("app-sg",
    vpc_id=vpc.id,
    description="Allow web and SSH traffic",
    ingress=[
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=80,
            to_port=80,
            cidr_blocks=["0.0.0.0/0"],
        ),
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=22,
            to_port=22,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ],
    egress=[
        aws.ec2.SecurityGroupEgressArgs(
            protocol="-1",
            from_port=0,
            to_port=0,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ],
)

# Create EC2 instance
app_server = aws.ec2.Instance("app-server",
    instance_type="t3.small",
    ami="ami-0c55b159cbfafe1f0",
    subnet_id=public_subnet.id,
    vpc_security_group_ids=[app_security_group.id],
    key_name="deployment-key",
    tags={
        "Name": "AppServer",
        "ManagedBy": "Pulumi"
    },
)

# Export the instance IP and DNS name
pulumi.export("server_ip", app_server.public_ip)
pulumi.export("server_dns", app_server.public_dns)
# Ansible playbook for server configuration (configure_app.yml)
---
- name: Configure application server
  hosts: app_servers
  become: yes
  vars:
    app_name: my_web_app
    app_version: 1.2.3
    
  tasks:
    # Install required packages
    - name: Install dependencies
      apt:
        name:
          - nginx
          - python3
          - python3-pip
          - supervisor
        state: present
        update_cache: yes
        
    # Deploy application
    - name: Clone application repository
      git:
        repo: "https://github.com/example/my_web_app.git"
        dest: "/opt/{{ app_name }}"
        version: "v{{ app_version }}"
        
    # Configure application
    - name: Install Python dependencies
      pip:
        requirements: "/opt/{{ app_name }}/requirements.txt"
        
    # Set up Nginx
    - name: Configure Nginx
      template:
        src: nginx_site.j2
        dest: "/etc/nginx/sites-available/{{ app_name }}"
      notify:
        - Reload Nginx
        
    - name: Enable Nginx site
      file:
        src: "/etc/nginx/sites-available/{{ app_name }}"
        dest: "/etc/nginx/sites-enabled/{{ app_name }}"
        state: link
      notify:
        - Reload Nginx
        
  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded
# Integration script (deploy.sh)
#!/bin/bash

# Step 1: Use Pulumi to provision infrastructure
echo "Provisioning infrastructure with Pulumi..."
cd infrastructure
pulumi up --yes
SERVER_IP=$(pulumi stack output server_ip)
echo "Server provisioned at $SERVER_IP"

# Step 2: Update Ansible inventory
echo "Updating Ansible inventory..."
echo "[app_servers]" > ../ansible/inventory
echo "$SERVER_IP ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/deployment-key.pem" >> ../ansible/inventory

# Step 3: Wait for server to be ready
echo "Waiting for server to be ready..."
sleep 60

# Step 4: Run Ansible to configure the server
echo "Configuring server with Ansible..."
cd ../ansible
ansible-playbook -i inventory configure_app.yml

echo "Deployment complete!"

This combined approach uses Pulumi’s strength in infrastructure provisioning and Ansible’s capability for server configuration, creating a comprehensive deployment pipeline.

Decision Framework: Choosing the Right Tool

To help you make the right choice for your specific needs, consider this decision framework:

  1. Primary Task Focus
    • Cloud infrastructure provisioning → Pulumi
    • Server configuration and application deployment → Ansible
    • Both, with clear separation → Consider using both together
  2. Team Skills and Preferences
    • Strong software development background → Pulumi
    • System administration background → Ansible
    • Mixed skills → Choose based on primary tasks or use both
  3. Infrastructure Type
    • Cloud-native resources → Pulumi
    • Traditional servers (physical or virtual) → Ansible
    • Mixed environment → Potentially both tools
  4. Workflow Requirements
    • Infrastructure lifecycle management → Pulumi
    • Continuous configuration and deployment → Ansible
    • Complete CI/CD pipeline → Consider integration of both
  5. Complexity and Scale
    • Complex, programmatic infrastructure logic → Pulumi
    • Large number of similar servers to configure → Ansible
    • Dynamic, changing requirements → Pulumi’s programming model may adapt better

Conclusion

Both Pulumi and Ansible serve essential but different roles in the modern IT automation landscape:

  • Pulumi excels at cloud infrastructure provisioning using familiar programming languages, offering powerful abstractions, type safety, and a comprehensive resource model for multi-cloud environments.
  • Ansible shines at server configuration, application deployment, and operational tasks with its agentless architecture, straightforward YAML syntax, and broad platform support.

Many organizations find value in using both tools together: Pulumi for provisioning the underlying cloud infrastructure and Ansible for configuring the resulting servers and deploying applications. This combination leverages the strengths of each tool and provides comprehensive coverage across the infrastructure lifecycle.

The right choice depends on your specific requirements, team skills, and existing infrastructure. By understanding the strengths and ideal use cases for each tool, you can make an informed decision that aligns with your organization’s needs and capabilities.


Keywords: Pulumi, Ansible, infrastructure as code, configuration management, cloud automation, server configuration, IaC, automation tools, DevOps, infrastructure provisioning, cloud infrastructure, configuration automation, YAML, programming languages

#Pulumi #Ansible #InfrastructureAsCode #ConfigurationManagement #DevOps #CloudAutomation #IaC #ServerConfiguration #CloudInfrastructure #Automation #DevOpsTools #CloudNative #InfrastructureAutomation #ServerProvisioning


By Alex

Leave a Reply

Your email address will not be published. Required fields are marked *