Ansible Deployment Guide
Ansible is a powerful open-source automation tool that simplifies server configuration and application deployment. Combined with RamNode's reliable VPS hosting, you can automate your entire infrastructure management with ease.
On This Page
Introduction
Ansible is an open-source automation tool that simplifies configuration management, application deployment, and task automation. Unlike other configuration management tools, Ansible uses an agentless architecture—requiring only SSH access to manage remote servers. This makes it ideal for managing RamNode VPS instances without installing additional software on your servers.
This guide will walk you through setting up Ansible on your local machine or control node and demonstrate practical examples for common hosting and server management tasks on RamNode infrastructure.
Why Use Ansible for RamNode VPS Management?
Agentless Architecture
No software installation required on managed nodes
Idempotent Operations
Safe to run playbooks multiple times without unintended changes
Simple YAML Syntax
Human-readable configuration files that are easy to version control
Multi-Server Management
Manage dozens or hundreds of VPS instances simultaneously
Extensive Module Library
Over 3,000 built-in modules for common tasks
Prerequisites
Before starting, ensure you have:
- A RamNode VPS (or multiple VPS instances) running Ubuntu 22.04/24.04, AlmaLinux 8/9, or Debian 11/12
- Root or sudo access to your RamNode VPS
- SSH key-based authentication configured (recommended)
- A control machine (your local computer or a dedicated management VPS) with Python 3.8+
Installing Ansible
Ubuntu/Debian Control Node
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansibleAlmaLinux/Rocky Linux Control Node
sudo dnf install -y epel-release
sudo dnf install -y ansiblemacOS Control Node
brew install ansiblePython pip (Universal)
python3 -m pip install --user ansibleVerify Installation
ansible --versionConfiguring SSH Access
For security and convenience, configure SSH key-based authentication:
Generate SSH Key Pair (if needed)
ssh-keygen -t ed25519 -C "ansible-control" -f ~/.ssh/ansible_keyCopy Key to RamNode VPS
ssh-copy-id -i ~/.ssh/ansible_key.pub root@your-ramnode-vps-ipTest Connection
ssh -i ~/.ssh/ansible_key root@your-ramnode-vps-ipBasic Ansible Configuration
Create Ansible Directory Structure
mkdir -p ~/ansible-ramnode/{inventory,playbooks,roles,group_vars,host_vars}
cd ~/ansible-ramnodeCreate Inventory File
Create inventory/hosts.ini:
[webservers]
web1.ramnode.local ansible_host=192.0.2.10
web2.ramnode.local ansible_host=192.0.2.11
[databases]
db1.ramnode.local ansible_host=192.0.2.20
[all:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/ansible_key
ansible_python_interpreter=/usr/bin/python3Create Ansible Configuration File
Create ansible.cfg:
[defaults]
inventory = ./inventory/hosts.ini
host_key_checking = False
retry_files_enabled = False
callback_whitelist = profile_tasks, timer
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = FalseTest Connectivity
ansible all -m pingExample 1: Initial Server Hardening
Create playbooks/server-hardening.yml:
---
- name: Harden RamNode VPS
hosts: all
become: yes
tasks:
- name: Update all packages
apt:
update_cache: yes
upgrade: dist
autoremove: yes
when: ansible_os_family == "Debian"
- name: Update all packages (RHEL)
dnf:
name: "*"
state: latest
when: ansible_os_family == "RedHat"
- name: Install security packages
package:
name:
- fail2ban
- ufw
- unattended-upgrades
state: present
- name: Configure UFW defaults
ufw:
direction: "{{ item.direction }}"
policy: "{{ item.policy }}"
loop:
- { direction: 'incoming', policy: 'deny' }
- { direction: 'outgoing', policy: 'allow' }
- name: Allow SSH through firewall
ufw:
rule: allow
port: '22'
proto: tcp
- name: Enable UFW
ufw:
state: enabled
- name: Start and enable fail2ban
systemd:
name: fail2ban
state: started
enabled: yes
- name: Disable root login via SSH
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
state: present
notify: restart sshd
handlers:
- name: restart sshd
systemd:
name: sshd
state: restartedRun the playbook:
ansible-playbook playbooks/server-hardening.ymlExample 2: LEMP Stack Deployment
Deploy Nginx, MySQL, and PHP on your RamNode VPS. Create playbooks/lemp-stack.yml:
---
- name: Deploy LEMP Stack
hosts: webservers
become: yes
vars:
mysql_root_password: "SecurePass123!"
php_version: "8.2"
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Install MySQL Server
apt:
name:
- mysql-server
- python3-pymysql
state: present
- name: Start MySQL service
systemd:
name: mysql
state: started
enabled: yes
- name: Set MySQL root password
mysql_user:
name: root
password: "{{ mysql_root_password }}"
login_unix_socket: /var/run/mysqld/mysqld.sock
state: present
- name: Add PHP repository
apt_repository:
repo: ppa:ondrej/php
state: present
- name: Install PHP and extensions
apt:
name:
- php{{ php_version }}-fpm
- php{{ php_version }}-mysql
- php{{ php_version }}-curl
- php{{ php_version }}-gd
- php{{ php_version }}-mbstring
- php{{ php_version }}-xml
- php{{ php_version }}-zip
state: present
update_cache: yes
- name: Configure Nginx default site
template:
src: templates/nginx-default.j2
dest: /etc/nginx/sites-available/default
notify: reload nginx
- name: Start and enable services
systemd:
name: "{{ item }}"
state: started
enabled: yes
loop:
- nginx
- php{{ php_version }}-fpm
- name: Allow HTTP and HTTPS
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- '80'
- '443'
handlers:
- name: reload nginx
systemd:
name: nginx
state: reloadedCreate templates/nginx-default.j2:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.php index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}Example 3: WordPress Deployment
Automate WordPress installation. Create playbooks/wordpress.yml:
---
- name: Deploy WordPress
hosts: webservers
become: yes
vars:
wp_domain: "example.com"
wp_db_name: "wordpress_db"
wp_db_user: "wp_user"
wp_db_password: "SecureDBPass123!"
wp_admin_email: "admin@example.com"
tasks:
- name: Download WordPress
get_url:
url: https://wordpress.org/latest.tar.gz
dest: /tmp/wordpress.tar.gz
- name: Extract WordPress
unarchive:
src: /tmp/wordpress.tar.gz
dest: /var/www/
remote_src: yes
creates: /var/www/wordpress
- name: Set ownership
file:
path: /var/www/wordpress
owner: www-data
group: www-data
recurse: yes
- name: Set directory permissions
file:
path: /var/www/wordpress
mode: '0755'
recurse: yes
state: directory
- name: Create WordPress database
mysql_db:
name: "{{ wp_db_name }}"
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Create WordPress DB user
mysql_user:
name: "{{ wp_db_user }}"
password: "{{ wp_db_password }}"
priv: "{{ wp_db_name }}.*:ALL"
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Copy wp-config.php
template:
src: templates/wp-config.php.j2
dest: /var/www/wordpress/wp-config.php
owner: www-data
group: www-data
mode: '0640'
- name: Configure Nginx for WordPress
template:
src: templates/wordpress-nginx.j2
dest: /etc/nginx/sites-available/{{ wp_domain }}
notify: reload nginx
- name: Enable WordPress site
file:
src: /etc/nginx/sites-available/{{ wp_domain }}
dest: /etc/nginx/sites-enabled/{{ wp_domain }}
state: link
notify: reload nginx
handlers:
- name: reload nginx
systemd:
name: nginx
state: reloadedExample 4: Docker Installation and Container Deployment
Install Docker and deploy a containerized application. Create playbooks/docker-deploy.yml:
---
- name: Install Docker and Deploy Container
hosts: all
become: yes
tasks:
- name: Install prerequisites
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
update_cache: yes
- 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] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable
state: present
- name: Install Docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: yes
- name: Start and enable Docker
systemd:
name: docker
state: started
enabled: yes
- name: Install Docker Python library
pip:
name:
- docker
- docker-compose
state: present
- name: Create application directory
file:
path: /opt/myapp
state: directory
mode: '0755'
- name: Deploy Nginx container
docker_container:
name: nginx-web
image: nginx:latest
state: started
restart_policy: always
ports:
- "8080:80"
volumes:
- /opt/myapp/html:/usr/share/nginx/html:ro
- name: Create index.html
copy:
content: |
<html>
<head><title>RamNode VPS</title></head>
<body>
<h1>Ansible on RamNode</h1>
<p>Server: {{ ansible_hostname }}</p>
</body>
</html>
dest: /opt/myapp/html/index.html
mode: '0644'Example 5: Multi-Server Database Replication
Configure MySQL master-slave replication across multiple RamNode VPS instances. Create playbooks/mysql-replication.yml:
---
- name: Configure MySQL Master
hosts: db_master
become: yes
vars:
repl_user: "replicator"
repl_password: "ReplPass123!"
tasks:
- name: Configure MySQL for replication
lineinfile:
path: /etc/mysql/mysql.conf.d/mysqld.cnf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: '^#?server-id', line: 'server-id = 1' }
- { regexp: '^#?log_bin', line: 'log_bin = /var/log/mysql/mysql-bin.log' }
- { regexp: '^#?binlog_do_db', line: 'binlog_do_db = production' }
notify: restart mysql
- name: Create replication user
mysql_user:
name: "{{ repl_user }}"
password: "{{ repl_password }}"
priv: "*.*:REPLICATION SLAVE"
host: "%"
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Get master status
mysql_replication:
mode: getmaster
login_unix_socket: /var/run/mysqld/mysqld.sock
register: master_status
- name: Save master status
set_fact:
master_log_file: "{{ master_status.File }}"
master_log_pos: "{{ master_status.Position }}"
handlers:
- name: restart mysql
systemd:
name: mysql
state: restarted
- name: Configure MySQL Slave
hosts: db_slave
become: yes
tasks:
- name: Configure MySQL for replication
lineinfile:
path: /etc/mysql/mysql.conf.d/mysqld.cnf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: '^#?server-id', line: 'server-id = 2' }
- { regexp: '^#?relay-log', line: 'relay-log = /var/log/mysql/mysql-relay-bin.log' }
notify: restart mysql
- name: Configure slave replication
mysql_replication:
mode: changeprimary
primary_host: "{{ hostvars[groups['db_master'][0]]['ansible_host'] }}"
primary_user: "{{ hostvars[groups['db_master'][0]]['repl_user'] }}"
primary_password: "{{ hostvars[groups['db_master'][0]]['repl_password'] }}"
primary_log_file: "{{ hostvars[groups['db_master'][0]]['master_log_file'] }}"
primary_log_pos: "{{ hostvars[groups['db_master'][0]]['master_log_pos'] }}"
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Start slave replication
mysql_replication:
mode: startreplica
login_unix_socket: /var/run/mysqld/mysqld.sock
handlers:
- name: restart mysql
systemd:
name: mysql
state: restartedExample 6: Automated Backups with Restic
Configure automated backups to Backblaze B2. Create playbooks/backup-setup.yml:
---
- name: Setup Restic Backups
hosts: all
become: yes
vars:
b2_account_id: "your_account_id"
b2_application_key: "your_app_key"
b2_bucket: "ramnode-backups"
restic_password: "SecureResticPass123!"
backup_paths:
- /var/www
- /etc
- /home
tasks:
- name: Download Restic
get_url:
url: https://github.com/restic/restic/releases/download/v0.16.2/restic_0.16.2_linux_amd64.bz2
dest: /tmp/restic.bz2
- name: Extract Restic
shell: bunzip2 -c /tmp/restic.bz2 > /usr/local/bin/restic
args:
creates: /usr/local/bin/restic
- name: Make Restic executable
file:
path: /usr/local/bin/restic
mode: '0755'
- name: Create Restic environment file
copy:
content: |
export RESTIC_REPOSITORY="b2:{{ b2_bucket }}:/"
export RESTIC_PASSWORD="{{ restic_password }}"
export B2_ACCOUNT_ID="{{ b2_account_id }}"
export B2_ACCOUNT_KEY="{{ b2_application_key }}"
dest: /root/.restic_env
mode: '0600'
- name: Initialize Restic repository
shell: |
source /root/.restic_env
restic snapshots || restic init
args:
executable: /bin/bash
register: init_result
changed_when: "'created restic repository' in init_result.stderr"
failed_when: false
- name: Create backup script
copy:
content: |
#!/bin/bash
source /root/.restic_env
# Run backup
restic backup {{ backup_paths | join(' ') }} \
--tag ramnode \
--exclude-caches \
--exclude '/var/log/*' \
--exclude '/tmp/*'
# Prune old backups
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
# Check repository
restic check
dest: /usr/local/bin/restic-backup.sh
mode: '0750'
- name: Create daily backup cron job
cron:
name: "Restic daily backup"
hour: "2"
minute: "0"
job: "/usr/local/bin/restic-backup.sh >> /var/log/restic-backup.log 2>&1"
user: rootExample 7: Monitoring with Prometheus and Grafana
Deploy a complete monitoring stack. Create playbooks/monitoring.yml:
---
- name: Deploy Monitoring Stack
hosts: monitoring
become: yes
tasks:
- name: Create monitoring directory
file:
path: /opt/monitoring
state: directory
mode: '0755'
- name: Create docker-compose.yml
copy:
content: |
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
restart: always
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_SERVER_ROOT_URL=http://{{ ansible_host }}:3000
restart: always
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
restart: always
volumes:
prometheus-data:
grafana-data:
dest: /opt/monitoring/docker-compose.yml
- name: Create Prometheus config
copy:
content: |
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
dest: /opt/monitoring/prometheus.yml
- name: Start monitoring stack
docker_compose:
project_src: /opt/monitoring
state: present
- name: Allow monitoring ports
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- '3000'
- '9090'
- '9100'Advanced Ansible Patterns
Using Ansible Vault for Sensitive Data
Encrypt sensitive variables:
# Create encrypted file
ansible-vault create group_vars/all/vault.yml
# Add sensitive data:
vault_mysql_root_password: "SuperSecretPass123!"
vault_b2_account_key: "your_secret_key"
# Reference in playbooks:
vars:
mysql_root_password: "{{ vault_mysql_root_password }}"
# Run with vault password:
ansible-playbook playbooks/lemp-stack.yml --ask-vault-passUsing Roles for Reusability
Create a reusable role structure:
ansible-galaxy init roles/common
ansible-galaxy init roles/webserver
ansible-galaxy init roles/databaseDynamic Inventory
For managing many RamNode VPS instances, create a dynamic inventory script:
#!/usr/bin/env python3
import json
import sys
def get_inventory():
inventory = {
'webservers': {
'hosts': ['web1.ramnode.local', 'web2.ramnode.local'],
'vars': {
'ansible_user': 'root'
}
},
'databases': {
'hosts': ['db1.ramnode.local'],
'vars': {
'ansible_user': 'root'
}
},
'_meta': {
'hostvars': {
'web1.ramnode.local': {
'ansible_host': '192.0.2.10'
},
'web2.ramnode.local': {
'ansible_host': '192.0.2.11'
},
'db1.ramnode.local': {
'ansible_host': '192.0.2.20'
}
}
}
}
return inventory
if __name__ == '__main__':
if len(sys.argv) == 2 and sys.argv[1] == '--list':
print(json.dumps(get_inventory(), indent=2))
else:
print(json.dumps({}))Best Practices for RamNode Deployments
- •Use Version Control: Store all playbooks in Git for change tracking and collaboration
- •Test in Staging: Always test playbooks on a development VPS before production
- •Use Check Mode: Run playbooks with
--checkto preview changes without executing - •Implement Idempotency: Ensure playbooks can run multiple times safely
- •Document Variables: Use clear variable names and maintain a README
- •Tag Your Tasks: Use tags to run specific parts of playbooks
- •Monitor Execution Time: Use callback plugins to identify slow tasks
- •Secure Credentials: Always use Ansible Vault for passwords and API keys
Troubleshooting Common Issues
Connection Refused
If Ansible cannot connect to your RamNode VPS:
# Test SSH connectivity directly
ssh -i ~/.ssh/ansible_key root@your-ip -vvv
# Verify inventory
ansible-inventory --list
# Test with increased verbosity
ansible all -m ping -vvvPython Not Found
Some minimal OS installations may not include Python:
# Install Python on Ubuntu/Debian
ansible all -m raw -a "apt update && apt install -y python3" -u root
# Install Python on AlmaLinux/Rocky
ansible all -m raw -a "dnf install -y python3" -u rootPermission Denied
If you encounter permission errors:
# Check become settings
ansible-playbook playbook.yml --ask-become-pass
# Or specify become explicitly
ansible-playbook playbook.yml -b --become-user=rootRecommended RamNode Plans
Ansible Control Node
For running Ansible and managing infrastructure
- • 2 vCPU / 4GB RAM (Standard Cloud VPS)
- • 40GB NVMe storage
- • Ubuntu 24.04 LTS
Web Servers
For LEMP stack and WordPress deployments
- • 2-4 vCPU / 4-8GB RAM
- • 80GB+ NVMe storage
- • Ubuntu 24.04 or AlmaLinux 9
Database Servers
For MySQL replication setups
- • 4 vCPU / 8GB RAM (Standard Cloud VPS)
- • 160GB+ NVMe storage
- • Block storage for data volumes
Monitoring Server
For Prometheus and Grafana stack
- • 2 vCPU / 4GB RAM
- • 80GB NVMe storage
- • Block storage for metrics retention
