AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible for PostgreSQL and MySQL: Database Automation Complete Guide

By Luca Berton · Published 2024-01-01 · Category: installation

Automate PostgreSQL and MySQL with Ansible. Create databases and users, manage permissions, configure replication, automate backups, tune performance.

Why Ansible for Database Automation?

Database setup and maintenance is repetitive, error-prone, and often done manually. Ansible automates the entire database lifecycle: installation, configuration, user/permission management, replication setup, backups, and performance tuning — all version-controlled and repeatable.

See also: Ansible for Database Automation: PostgreSQL, MySQL, MongoDB, and Redis

Collections

# PostgreSQL
ansible-galaxy collection install community.postgresql

# MySQL / MariaDB ansible-galaxy collection install community.mysql

# Dependencies on control node pip install psycopg2-binary # PostgreSQL pip install PyMySQL # MySQL/MariaDB

PostgreSQL

Install PostgreSQL

---
- name: Install and configure PostgreSQL
  hosts: db_servers
  become: true

vars: pg_version: 16 pg_data_dir: "/var/lib/postgresql/{{ pg_version }}/main" pg_conf_dir: "/etc/postgresql/{{ pg_version }}/main"

tasks: - name: Install PostgreSQL ansible.builtin.apt: name: - "postgresql-{{ pg_version }}" - "postgresql-client-{{ pg_version }}" - "postgresql-contrib-{{ pg_version }}" - python3-psycopg2 state: present update_cache: true

- name: Start PostgreSQL ansible.builtin.systemd: name: postgresql state: started enabled: true

Configure postgresql.conf

    - name: Configure PostgreSQL
      ansible.builtin.lineinfile:
        path: "{{ pg_conf_dir }}/postgresql.conf"
        regexp: "^#?{{ item.key }} ="
        line: "{{ item.key }} = {{ item.value }}"
      loop:
        - { key: listen_addresses, value: "'*'" }
        - { key: max_connections, value: "200" }
        - { key: shared_buffers, value: "'4GB'" }
        - { key: effective_cache_size, value: "'12GB'" }
        - { key: work_mem, value: "'64MB'" }
        - { key: maintenance_work_mem, value: "'1GB'" }
        - { key: wal_level, value: "'replica'" }
        - { key: max_wal_senders, value: "5" }
        - { key: max_replication_slots, value: "5" }
        - { key: log_min_duration_statement, value: "1000" }
        - { key: log_checkpoints, value: "on" }
        - { key: log_connections, value: "on" }
        - { key: log_disconnections, value: "on" }
      notify: restart postgresql

- name: Configure pg_hba.conf community.postgresql.postgresql_pg_hba: dest: "{{ pg_conf_dir }}/pg_hba.conf" contype: host users: "{{ item.user }}" source: "{{ item.source }}" databases: "{{ item.db }}" method: scram-sha-256 loop: - { user: app_user, source: "10.0.0.0/24", db: app_db } - { user: readonly, source: "10.0.0.0/24", db: app_db } - { user: replication, source: "10.0.1.0/24", db: replication } notify: reload postgresql

handlers: - name: restart postgresql ansible.builtin.systemd: name: postgresql state: restarted

- name: reload postgresql ansible.builtin.systemd: name: postgresql state: reloaded

Create Databases and Users

    - name: Create application database
      community.postgresql.postgresql_db:
        name: app_db
        encoding: UTF-8
        lc_collate: en_US.UTF-8
        lc_ctype: en_US.UTF-8
        template: template0
        state: present
      become_user: postgres

- name: Create application user community.postgresql.postgresql_user: name: app_user password: "{{ vault_pg_app_password }}" encrypted: true state: present become_user: postgres no_log: true

- name: Grant privileges community.postgresql.postgresql_privs: database: app_db privs: ALL type: database role: app_user state: present become_user: postgres

- name: Grant schema privileges community.postgresql.postgresql_privs: database: app_db privs: ALL type: schema objs: public role: app_user state: present become_user: postgres

- name: Create read-only user community.postgresql.postgresql_user: name: readonly password: "{{ vault_pg_readonly_password }}" state: present become_user: postgres no_log: true

- name: Grant read-only access community.postgresql.postgresql_privs: database: app_db privs: SELECT type: table objs: ALL_IN_SCHEMA schema: public role: readonly state: present become_user: postgres

Extensions

    - name: Install extensions
      community.postgresql.postgresql_ext:
        name: "{{ item }}"
        db: app_db
        state: present
      loop:
        - pg_stat_statements
        - pgcrypto
        - uuid-ossp
        - pg_trgm
      become_user: postgres

Backup Automation

---
- name: PostgreSQL backup
  hosts: db_servers
  become: true
  become_user: postgres

vars: backup_dir: /backups/postgresql retention_days: 30

tasks: - name: Create backup directory ansible.builtin.file: path: "{{ backup_dir }}" state: directory mode: '0700' owner: postgres

- name: Full database dump community.postgresql.postgresql_db: name: app_db state: dump target: "{{ backup_dir }}/app_db_{{ ansible_date_time.date }}.sql.gz"

- name: Clean old backups ansible.builtin.find: paths: "{{ backup_dir }}" patterns: "*.sql.gz" age: "{{ retention_days }}d" register: old_backups

- name: Remove old backups ansible.builtin.file: path: "{{ item.path }}" state: absent loop: "{{ old_backups.files }}"

- name: Deploy backup cron job ansible.builtin.cron: name: "PostgreSQL daily backup" minute: "0" hour: "2" job: "pg_dump -Fc app_db | gzip > {{ backup_dir }}/app_db_$(date +\\%Y-\\%m-\\%d).sql.gz" user: postgres

Streaming Replication

---
- name: Configure PostgreSQL primary
  hosts: pg_primary
  become: true
  tasks:
    - name: Create replication user
      community.postgresql.postgresql_user:
        name: replication
        password: "{{ vault_replication_password }}"
        role_attr_flags: REPLICATION,LOGIN
      become_user: postgres

- name: Create replication slot community.postgresql.postgresql_slot: name: replica_slot_1 slot_type: physical state: present become_user: postgres

--- - name: Configure PostgreSQL replica hosts: pg_replica become: true tasks: - name: Stop PostgreSQL ansible.builtin.systemd: name: postgresql state: stopped

- name: Remove existing data ansible.builtin.file: path: "{{ pg_data_dir }}" state: absent

- name: Base backup from primary ansible.builtin.command: cmd: > pg_basebackup -h {{ hostvars['pg_primary']['ansible_host'] }} -U replication -D {{ pg_data_dir }} -Fp -Xs -P -R --slot=replica_slot_1 become_user: postgres environment: PGPASSWORD: "{{ vault_replication_password }}"

- name: Start PostgreSQL replica ansible.builtin.systemd: name: postgresql state: started

See also: ansible.mysql 5.0.0 Release: Migrate from community.mysql Collection

MySQL / MariaDB

Install MySQL

---
- name: Install MySQL 8
  hosts: mysql_servers
  become: true

tasks: - name: Install MySQL ansible.builtin.apt: name: - mysql-server-8.0 - mysql-client-8.0 - python3-pymysql state: present update_cache: true

- name: Start MySQL ansible.builtin.systemd: name: mysql state: started enabled: true

- name: Set root password community.mysql.mysql_user: name: root password: "{{ vault_mysql_root_password }}" host: localhost login_unix_socket: /var/run/mysqld/mysqld.sock state: present no_log: true

- name: Create .my.cnf for root ansible.builtin.template: src: my.cnf.j2 dest: /root/.my.cnf mode: '0600'

Create Databases and Users

    - name: Create application database
      community.mysql.mysql_db:
        name: app_db
        encoding: utf8mb4
        collation: utf8mb4_unicode_ci
        state: present
        login_unix_socket: /var/run/mysqld/mysqld.sock

- name: Create application user community.mysql.mysql_user: name: app_user password: "{{ vault_mysql_app_password }}" host: "10.0.0.%" priv: "app_db.*:SELECT,INSERT,UPDATE,DELETE,CREATE,ALTER,INDEX,DROP" state: present login_unix_socket: /var/run/mysqld/mysqld.sock no_log: true

- name: Create read-only user community.mysql.mysql_user: name: readonly password: "{{ vault_mysql_readonly_password }}" host: "10.0.0.%" priv: "app_db.*:SELECT" state: present login_unix_socket: /var/run/mysqld/mysqld.sock no_log: true

- name: Create backup user community.mysql.mysql_user: name: backup password: "{{ vault_mysql_backup_password }}" host: localhost priv: "*.*:SELECT,SHOW VIEW,TRIGGER,LOCK TABLES,PROCESS,RELOAD" state: present login_unix_socket: /var/run/mysqld/mysqld.sock no_log: true

Configure MySQL

    - name: Deploy MySQL configuration
      ansible.builtin.template:
        src: mysqld.cnf.j2
        dest: /etc/mysql/mysql.conf.d/mysqld.cnf
        mode: '0644'
      notify: restart mysql

# templates/mysqld.cnf.j2 # [mysqld] # bind-address = 0.0.0.0 # max_connections = 200 # innodb_buffer_pool_size = 4G # innodb_log_file_size = 1G # innodb_flush_log_at_trx_commit = 1 # slow_query_log = 1 # slow_query_log_file = /var/log/mysql/slow.log # long_query_time = 2 # log_error = /var/log/mysql/error.log # server-id = {{ mysql_server_id }} # log_bin = /var/log/mysql/mysql-bin # binlog_format = ROW # expire_logs_days = 7

handlers: - name: restart mysql ansible.builtin.systemd: name: mysql state: restarted

Run SQL Queries

    - name: Run schema migration
      community.mysql.mysql_query:
        login_db: app_db
        query:
          - CREATE TABLE IF NOT EXISTS users (
              id BIGINT AUTO_INCREMENT PRIMARY KEY,
              email VARCHAR(255) NOT NULL UNIQUE,
              name VARCHAR(255),
              created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB;
          - CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
        login_unix_socket: /var/run/mysqld/mysqld.sock

- name: Import SQL file community.mysql.mysql_db: name: app_db state: import target: /tmp/schema.sql login_unix_socket: /var/run/mysqld/mysqld.sock

MySQL Backup

    - name: Backup MySQL database
      community.mysql.mysql_db:
        name: app_db
        state: dump
        target: "/backups/mysql/app_db_{{ ansible_date_time.date }}.sql.gz"
        login_unix_socket: /var/run/mysqld/mysqld.sock

- name: Deploy backup script ansible.builtin.copy: content: | #!/bin/bash BACKUP_DIR=/backups/mysql DATE=$(date +%Y-%m-%d) mysqldump --single-transaction --routines --triggers app_db | gzip > ${BACKUP_DIR}/app_db_${DATE}.sql.gz find ${BACKUP_DIR} -name "*.sql.gz" -mtime +30 -delete dest: /usr/local/bin/mysql-backup.sh mode: '0755'

- name: Schedule backup ansible.builtin.cron: name: "MySQL daily backup" minute: "0" hour: "3" job: /usr/local/bin/mysql-backup.sh

Database Security Hardening

---
- name: Harden database security
  hosts: db_servers
  become: true
  tasks:
    # PostgreSQL
    - name: Remove default postgres trust auth
      community.postgresql.postgresql_pg_hba:
        dest: /etc/postgresql/16/main/pg_hba.conf
        contype: local
        users: all
        databases: all
        method: scram-sha-256
      when: "'postgresql' in group_names"

# MySQL — remove anonymous users and test database - name: Remove anonymous MySQL users community.mysql.mysql_user: name: "" host_all: true state: absent login_unix_socket: /var/run/mysqld/mysqld.sock when: "'mysql' in group_names"

- name: Remove test database community.mysql.mysql_db: name: test state: absent login_unix_socket: /var/run/mysqld/mysqld.sock when: "'mysql' in group_names"

- name: Disallow root remote login community.mysql.mysql_user: name: root host: "%" state: absent login_unix_socket: /var/run/mysqld/mysqld.sock when: "'mysql' in group_names"

See also: Ansible for PostgreSQL: PGMeetUp Bari 2022 Highlights

FAQ

Should I use become_user: postgres or login_user?

For PostgreSQL, become_user: postgres uses Unix socket authentication (peer auth), which is the default and most secure for local connections. Use login_user/login_password for remote connections or when peer auth isn't configured.

How do I handle database migrations with Ansible?

For simple schema changes, use community.postgresql.postgresql_query or community.mysql.mysql_query. For complex applications, use a dedicated migration tool (Alembic, Flyway, Liquibase) and call it from Ansible with ansible.builtin.command. Ansible handles infrastructure, the migration tool handles schema versioning.

Can Ansible manage RDS / Cloud SQL?

Yes. Use the same community.postgresql and community.mysql modules with login_host, login_user, and login_password pointed at your cloud database endpoint. For provisioning the database instance itself, use amazon.aws.rds_instance, azure.azcollection.azure_rm_postgresqlserver, or google.cloud.gcp_sql_instance.

How do I encrypt database passwords in Ansible?

Use Ansible Vault: ansible-vault encrypt_string 'my-password' --name vault_pg_password. Store encrypted values in group_vars/db_servers/vault.yml and reference them as {{ vault_pg_password }}. Always use no_log: true on tasks that handle passwords.

Conclusion

Ansible automates the complete database lifecycle — PostgreSQL and MySQL installation, configuration tuning, user and permission management, replication setup, backup automation, and security hardening. Use community.postgresql and community.mysql collections for idempotent database management. Combine with Ansible Vault for secrets, scheduled backups via cron, and streaming replication for high availability.

Related Articles

Ansible for SAP HANA and S/4HANAAnsible Vault Complete GuideAAP 2.6 Backup and Disaster RecoveryAnsible for Docker and PodmanInstall Ansible Complete GuideAnsible for PostgreSQL: PGMeetUp Bari 2022

See also

Ansible for Database Automation: PostgreSQL, MySQL, MongoDB, and Redis

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home