Featured image of post Ansible Automation for VPS: Build Your Infrastructure Management from Scratch

Ansible Automation for VPS: Build Your Infrastructure Management from Scratch

Stop logging into every server manually! This guide walks you through using Ansible for bulk VPS deployment, configuration management, and automated operations — from beginner to production-ready.

Introduction: Why You Need Ansible

Imagine you have 10 VPS instances, each requiring Docker installation, Nginx reverse proxy setup, SSL certificate configuration, and regular security patching. Doing this manually could take hours or even days.

Ansible lets you write a single configuration file and execute the same operations across all your VPS instances — this is the core philosophy of Infrastructure as Code (IaC).

Manual Operations → Error-prone, time-consuming, unreproducible
Ansible Operations → One-click deployment, version-controlled, auditable

What Is Ansible?

Ansible is an open-source automation tool maintained by Red Hat, featuring:

  • Agentless Architecture: No agent required on target servers — connects via SSH
  • Declarative Configuration: Describe the desired end state; Ansible handles the differences
  • YAML Syntax: Human-readable configuration language with a gentle learning curve
  • Rich Module Library: Over 3,000 built-in modules covering virtually every ops scenario

Environment Prerequisites

Control Node Requirements

Ansible runs on a Control Node, which can be your local machine or any VPS:

ComponentRecommended Config
OSUbuntu 22.04+ / Debian 12+ / CentOS Stream 9
PythonPython 3.8+
Memory2GB+
Disk10GB+ (for playbooks and inventory)

Managed Node Requirements

Target VPS only need:

  • Linux: Python 3 or Python 2.7 + Jinja2
  • Windows: WinRM + PowerShell (optional)
  • Network: Control node must reach all managed nodes via SSH

Step 1: Install Ansible

# Ubuntu/Debian
sudo apt update
sudo apt install -y python3-pip sshpass
pip3 install --user ansible

# CentOS/RHEL
sudo dnf install -y epel-release
sudo dnf install -y ansible sshpass

# Verify installation
ansible --version

Step 2: Configure SSH Key-Based Authentication

This is critical for Ansible’s efficiency. Generate SSH keys on the control node and distribute them:

# 1. Generate key pair (if you don't have one)
ssh-keygen -t ed25519 -C "ansible@control-node" -N ""

# 2. Distribute public key to all VPS instances
ssh-copy-id -o StrictHostKeyChecking=no user@vps1.example.com
ssh-copy-id -o StrictHostKeyChecking=no user@vps2.example.com
ssh-copy-id -o StrictHostKeyChecking=no user@vps3.example.com

# 3. Test connectivity
ansible all -m ping

Output SUCCESS means everything is configured correctly!

Step 3: Create Inventory File

The inventory file defines which hosts Ansible manages and how they’re grouped:

# inventory.ini
[webservers]
vps1.example.com ansible_host=1.2.3.4
vps2.example.com ansible_host=5.6.7.8

[databases]
db1.example.com ansible_host=9.10.11.12

[monitoring]
mon1.example.com ansible_host=13.14.15.16

[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3

You can group by business logic:

[production]
web-prod-1.example.com
web-prod-2.example.com
db-prod-1.example.com

[staging]
web-stg-1.example.com
db-stg-1.example.com

[production:children]
webservers
databases

Step 4: Write Your First Playbook

A Playbook is the heart of Ansible — an automation script written in YAML. Here’s a complete example:

Basic System Hardening Playbook

# playbook.yml
---
- name: Basic System Hardening
  hosts: all
  become: true
  vars:
    timezone: Asia/Shanghai
    ntp_servers:
      - 0.cn.pool.ntp.org
      - 1.cn.pool.ntp.org
    firewall_allowed_ports:
      - 22/tcp
      - 80/tcp
      - 443/tcp

  tasks:
    - name: Update system packages
      apt:
        update_cache: yes
        upgrade: dist
      when: ansible_os_family == "Debian"

    - name: Install common tools
      apt:
        name:
          - curl
          - wget
          - htop
          - jq
          - vim
          - git
          - unzip
        state: present

    - name: Configure timezone
      timezone:
        timezone: "{{ timezone }}"

    - name: Configure NTP time sync
      systemd:
        name: chrony
        state: started
        enabled: yes
      when: ansible_os_family == "RedHat"

    - name: Create admin user
      user:
        name: admin
        groups: sudo
        shell: /bin/bash
        state: present

    - name: Harden SSH configuration
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
        state: present
      loop:
        - { regexp: "^#?PermitRootLogin", line: "PermitRootLogin no" }
        - { regexp: "^#?PasswordAuthentication", line: "PasswordAuthentication no" }
      notify: Restart SSH

    - name: Configure firewall (UFW)
      ufw:
        rule: allow
        port: "{{ item }}"
      loop: "{{ firewall_allowed_ports }}"

  handlers:
    - name: Restart SSH
      service:
        name: sshd
        state: restarted

Running the Playbook

# Syntax check (dry-run)
ansible-playbook playbook.yml --check --diff

# Execute
ansible-playbook playbook.yml -i inventory.ini -v

# Target specific group
ansible-playbook playbook.yml -i inventory.ini --limit webservers

Step 5: Docker Container Deployment

Most modern VPS run Docker containers. This Playbook automates the entire process:

# docker-setup.yml
---
- name: Install and Configure Docker
  hosts: all
  become: true

  tasks:
    - name: Install Docker dependencies
      apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
          - lsb-release
        state: present

    - name: Add Docker GPG key
      apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present

    - name: Add Docker repository
      apt_repository:
        repo: >-
          deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg]
          https://download.docker.com/linux/ubuntu
          {{ ansible_lsb.codename | lower }} stable
        state: present

    - name: Install Docker Engine
      apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
          - docker-compose-plugin
        state: present
        update_cache: yes

    - name: Start Docker service
      systemd:
        name: docker
        state: started
        enabled: yes

    - name: Configure Docker mirror
      copy:
        content: |
          {
            "registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
          }
        dest: /etc/docker/daemon.json
      notify: Restart Docker

    - name: Allow current user to manage Docker
      user:
        name: "{{ ansible_user }}"
        groups: docker
        append: yes

  handlers:
    - name: Restart Docker
      systemd:
        name: docker
        state: restarted

Step 6: Nginx Reverse Proxy Template

Use Ansible’s templating to dynamically generate Nginx configs based on variables:

# templates/nginx-site.conf.j2
server {
    listen 80;
    server_name {{ domain }};

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://{{ domain }}$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name {{ domain }};

    ssl_certificate     /etc/letsencrypt/live/{{ domain }}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{ domain }}/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    {% if enable_gzip %}
    gzip on;
    gzip_types text/plain application/json application/javascript text/css;
    {% endif %}

    location / {
        proxy_pass http://{{ backend_service }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Corresponding Playbook task:

- name: Deploy Nginx site config
  template:
    src: nginx-site.conf.j2
    dest: /etc/nginx/sites-available/{{ domain }}
    owner: root
    group: root
    mode: '0644'
  notify: Reload Nginx

- name: Enable site
  file:
    src: /etc/nginx/sites-available/{{ domain }}
    dest: /etc/nginx/sites-enabled/{{ domain }}
    state: link
  notify: Reload Nginx

Step 7: Automatic SSL Certificate Renewal

Let’s Encrypt certificates are valid for only 90 days. Ansible can automate renewal:

# certbot-setup.yml
---
- name: Install and Configure Certbot
  hosts: all
  become: true

  tasks:
    - name: Install Certbot
      apt:
        name: certbot
        state: present

    - name: Request SSL certificate
      community.general.certbot:
        command: certificate_only
        email: admin@{{ domain }}
        domains:
          - "{{ domain }}"
          - "www.{{ domain }}"
        challenge_hook: "systemctl reload nginx"
        account_sid: my-account
        state: present

    - name: Create renewal script
      copy:
        content: |
          #!/bin/bash
          certbot renew --quiet --post-hook "systemctl reload nginx"
          logger "Certificate renewal completed"
        dest: /usr/local/bin/renew-certs.sh
        mode: '0755'

    - name: Schedule cron job (daily at 3 AM)
      cron:
        name: "Auto-renew SSL certificates"
        minute: "0"
        hour: "3"
        job: "/usr/local/bin/renew-certs.sh >> /var/log/certbot-renew.log 2>&1"

Step 8: Multi-Environment Management

Different configurations for production and development environments:

project/
├── inventory/
│   ├── production.ini
│   ├── staging.ini
│   └── development.ini
├── group_vars/
│   ├── all.yml          # Global variables
│   ├── webservers.yml   # Web server variables
│   └── databases.yml    # Database variables
├── host_vars/
│   ├── vps1.example.com.yml
│   └── db1.example.com.yml
├── roles/
│   ├── nginx/
│   ├── docker/
│   ├── certbot/
│   └── monitoring/
└── site.yml             # Main Playbook

Variable Priority

# group_vars/all.yml
ntp_servers:
  - 0.cn.pool.ntp.org
  - 1.cn.pool.ntp.org

# host_vars/vps1.example.com.yml (overrides global)
ntp_servers:
  - ntp1.mycompany.com
  - ntp2.mycompany.com

Step 9: Monitoring Setup

Deploy Uptime Kuma or Prometheus Node Exporter on each VPS:

# monitoring-setup.yml
---
- name: Deploy monitoring agents
  hosts: all
  become: true

  tasks:
    - name: Deploy Node Exporter
      docker_container:
        name: node-exporter
        image: prom/node-exporter:latest
        restart_policy: always
        ports:
          - "9100:9100"
        volumes:
          - /proc:/host/proc:ro
          - /sys:/host/sys:ro
          - /:/rootfs:ro

    - name: Deploy Uptime Kuma
      docker_container:
        name: uptime-kuma
        image: louislam/uptime-kuma:1
        restart_policy: always
        ports:
          - "3001:3001"
        volumes:
          - uptime-kuma-data:/app/data

Step 10: CI/CD Integration

Integrate Ansible with GitHub Actions for automatic deployments:

# .github/workflows/deploy.yml
name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Ansible
        run: pip3 install ansible

      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H ${{ secrets.VPS_HOST }} >> ~/.ssh/known_hosts

      - name: Run Playbook
        run: |
          ansible-playbook site.yml \
            -i inventory/production.ini \
            -e "deploy_version=${{ github.sha }}" \
            -v

Best Practices & Pitfalls

✅ Do’s

  1. Organize code with Roles: Encapsulate each function as an independent role for reusability
  2. Dry-run first: Always use --check --diff to preview changes
  3. Version control Playbooks: Manage infrastructure like you manage code
  4. Principle of least privilege: Use sudo users instead of root
  5. Use Ansible Vault for sensitive data: Never store passwords or API keys in plain text
# Encrypt sensitive variables
ansible-vault encrypt group_vars/secrets.yml
ansible-vault edit group_vars/secrets.yml  # Edit encrypted file

# Decrypt at runtime
ansible-playbook site.yml --ask-vault-pass

❌ Don’ts

  1. Avoid overusing the raw module: Prefer standard modules
  2. Don’t hardcode IP addresses in Playbooks: Use Inventory for host management
  3. Don’t ignore error handling: Be cautious with ignore_errors
  4. Don’t modify too many hosts at once: Test on a single machine first
  5. Don’t skip documentation: Every Playbook should have clear comments

Cost Comparison: Manual vs Ansible

MetricManual OperationsAnsible Automation
Initial setup (10 VPS)~8 hours~30 minutes
Bulk security patches~4 hours~5 minutes
New server provisioning~2 hours~10 minutes
Error probabilityHigh (human error)Extremely low (consistent execution)
Audit trailNoneComplete log records
Monthly time saved-20+ hours

Advanced: Ansible AWX / Tower

When managing more than 50 VPS instances, consider AWX (the open-source enterprise web UI for Ansible):

# awx-deploy.yml
---
- name: Deploy AWX Management Panel
  hosts: localhost
  connection: local
  tasks:
    - name: Clone AWX repository
      git:
        repo: https://github.com/ansible/awx.git
        dest: /opt/awx
        version: latest

    - name: Build AWX
      shell: make deploy
      args:
        chdir: /opt/awx

AWX provides:

  • Web GUI to manage all Playbooks
  • Visual Inventory and host grouping
  • Scheduled tasks and scheduler
  • User permission management
  • Execution history and audit logs

Conclusion

Ansible is the Swiss Army knife of VPS operations — write once, execute everywhere. From single-server hardening to multi-machine clusters, from container orchestration to certificate management, Ansible handles it all.

Action Plan:

  1. Install Ansible on your first VPS today
  2. Write Playbooks for your most repetitive tasks
  3. Gradually add other VPS to your management scope
  4. Use Vault to protect sensitive information
  5. Push Playbooks to Git for version control

Remember: The best automation starts by solving one pain point. Don’t try to automate everything at once — start small and expand gradually.


Enjoyed this article? Follow selfvps.net for more self-hosting and VPS cost-saving tips!

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials