DEV Community

Cover image for Automating Ghost CMS + MariaDB with Ansible (the Smart Way)
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on

Automating Ghost CMS + MariaDB with Ansible (the Smart Way)

Hi there! I'm Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.


Spinning up a Ghost blog manually is cool until you have to do it three times in a row.

Whether you're scaling, rebuilding, or just love your infra reproducible, Ansible + Ghost is a match made in automation heaven.

In this post, I’ll walk you through setting up Ghost CMS 5.x on Ubuntu 22.04 using Ansible with:

  • MariaDB 10.6 as the database
  • Full systemd-based process setup
  • Ghost CLI installation
  • Email SMTP config
  • Sane default user permissions
  • And yes — restoring Ghost from a backup

Project Layout

We scaffold our Ansible role using Galaxy:

ansible-galaxy init roles/ghost --offline 
Enter fullscreen mode Exit fullscreen mode

Here's what the final structure looks like:

hex-ansible ├─ ghost.yml ├─ hosts.ini ├─ roles │ └─ ghost/ │ ├─ tasks/ │ ├─ templates/ │ ├─ vars/ │ ├─ files/ │ └─ ... 
Enter fullscreen mode Exit fullscreen mode

Variables: vars/main.yml

All our configurable values in one place:

ghost_user: ghost node_major: 18 ghost_version: "5.89.3" cli_version: 1.27.0 ghost_dir: /var/www/ghost ghost_url: https://journal.hexmos.com db_user: ghostuser db_pass: ghostpass123 db_name: ghostdb db_host: 127.0.0.1 restore_url: https://backup-bucket.example.com/ghost.zip mail_transport: "SMTP" mail_from: "'Ghost Journal' ghost@example.com" mail_host: "smtp.example.com" mail_port: 2465 mail_secure: true mail_user: "SMTP_USER" mail_pass: "SMTP_PASS" 
Enter fullscreen mode Exit fullscreen mode

Task Breakdown

1. Install MariaDB (install_mariadb.yml)

This handles:

  • Repo + GPG key
  • Installing MariaDB 10.6
  • Creating the DB and user
- name: Install MariaDB 10.6 apt: name: mariadb-server state: present - name: Create DB and user shell: | mysql -u root <<EOF CREATE DATABASE IF NOT EXISTS {{ db_name }}; CREATE USER IF NOT EXISTS '{{ db_user }}'@'{{ db_host }}' IDENTIFIED BY '{{ db_pass }}'; GRANT ALL PRIVILEGES ON {{ db_name }}.* TO '{{ db_user }}'@'{{ db_host }}'; FLUSH PRIVILEGES; EOF 
Enter fullscreen mode Exit fullscreen mode

2. Install Ghost & Node.js (install_ghost.yml)

Key steps:

  • Add NodeSource repo
  • Install Ghost CLI globally
  • Allow passwordless sudo for systemd ops
  • Install Ghost using the CLI as the ghost user
- name: Install Ghost as ghost user become_user: "{{ ghost_user }}" shell: | cd {{ ghost_dir }} && \ ghost install {{ ghost_version }} \ --url {{ ghost_url }} \ --db mysql \ --dbhost {{ db_host }} \ --dbuser {{ db_user }} \ --dbpass {{ db_pass }} \ --dbname {{ db_name }} \ --no-setup-nginx \ --no-setup-ssl \ --process systemd \ --start \ --no-prompt args: chdir: "{{ ghost_dir }}" creates: "{{ ghost_dir }}/current" 
Enter fullscreen mode Exit fullscreen mode

Also, to avoid sudo prompts for ghost:

- name: Allow ghost user passwordless sudo copy: dest: /etc/sudoers.d/ghost content: | ghost ALL=(ALL) NOPASSWD: /bin/systemctl ghost ALL=(ALL) NOPASSWD: /bin/systemctl, /bin/mv, /usr/bin/mkdir, /usr/bin/chown mode: '0440' validate: 'visudo -cf %s' 
Enter fullscreen mode Exit fullscreen mode

3. Restore Ghost Backup (restore_ghost.yml)

Download + extract your backup content:

- name: Download and unarchive ghost backup unarchive: src: "{{ restore_url }}" dest: "{{ ghost_dir }}/content" remote_src: true 
Enter fullscreen mode Exit fullscreen mode

Make sure permissions are set correctly:

- name: Fix permissions file: path: "{{ ghost_dir }}/content" owner: "{{ ghost_user }}" group: "{{ ghost_user }}" recurse: yes 
Enter fullscreen mode Exit fullscreen mode

Then drop in the config:

- name: Template config.production.json template: src: config.production.json.j2 dest: "{{ ghost_dir }}/config.production.json" owner: "{{ ghost_user }}" group: "{{ ghost_user }}" mode: "0640" notify: restart ghost 
Enter fullscreen mode Exit fullscreen mode

Ghost Config Template (config.production.json.j2)

{ "url": "{{ ghost_url }}", "database": { "client": "mysql", "connection": { "host": "{{ db_host }}", "user": "{{ db_user }}", "password": "{{ db_pass }}", "database": "{{ db_name }}" } }, "mail": { "transport": "{{ mail_transport }}", "options": { "from": {{ mail_from | to_json }}, "host": "{{ mail_host }}", "port": {{ mail_port }}, "secure": {{ mail_secure | to_json }}, "auth": { "user": "{{ mail_user }}", "pass": "{{ mail_pass }}" } } }, "process": "systemd", "paths": { "contentPath": "{{ ghost_dir }}/content" } } 
Enter fullscreen mode Exit fullscreen mode

Handler (handlers/main.yml)

- name: restart ghost command: ghost restart args: chdir: "{{ ghost_dir }}" become: true become_user: "{{ ghost_user }}" 
Enter fullscreen mode Exit fullscreen mode

Running the Playbook

Create a simple hosts.ini:

[ghost] your.server.ip ansible_user=ubuntu 
Enter fullscreen mode Exit fullscreen mode

Then trigger it:

ansible-playbook -i hosts.ini ghost.yml 
Enter fullscreen mode Exit fullscreen mode

Summary

This setup gives you:

  • A clean MariaDB + Ghost CMS stack on any Ubuntu box
  • Automated Ghost install using CLI with systemd
  • SMTP-ready configuration
  • Backup restore support
  • No sudo prompts ever again for systemctl

🔁 Want to scale this with nginx, SSL (Certbot), or Cloudflare? Plug those in as separate roles.

Pro Tips

  • Use Ansible Vault for secrets like SMTP credentials.
  • Schedule ghost backup via cron + Ansible-managed scripts.
  • Ghost upgrades? Just ghost update inside a task + service restart.

LiveAPI helps you get all your backend APIs documented in a few minutes.

With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.

LiveAPI Demo

If you're tired of updating Swagger manually or syncing Postman collections, give it a shot.

Top comments (2)

Collapse
 
nevodavid profile image
Nevo David

insane amount of actual how-to here, gotta admit seeing more infra get automated makes me want to redo my whole personal stack - you ever hit a point where adding automation feels like more work than doing it by hand for a while or nah

Collapse
 
dotallio profile image
Dotallio

Really clean automation flow! Love how you handled sudo permissions and backup restores. Have you tried plugging in SSL or nginx to this role yet, or is that your next step?