Comprehensive Ansible automation for deploying Mixpost v2.4.0 on Debian 10+, Ubuntu 20.04+, and RHEL 9+ compatible hosts. This playbook includes Docker Compose deployment, security hardening, TLS automation, and monitoring setup.
Current Version: v2.4.0
Ansible Minimum: 2.14+
Estimated Deployment: 20-30 minutes
- Prerequisites
- Inventory Configuration
- Variables Reference
- Complete Playbook
- Security Hardening Role
- Deployment Execution
- Post-Deployment
- Troubleshooting
| Component |
Version |
| Ansible |
2.14+ |
| Python |
3.9+ |
| OS |
Linux, macOS, WSL2 |
| Component |
Minimum |
Recommended |
| OS |
Debian 10+, Ubuntu 20.04+, RHEL 9+ |
Debian 12, Ubuntu 22.04, RHEL 9 |
| CPU |
2 cores |
4 cores |
| RAM |
2 GB |
4 GB |
| Disk |
20 GB |
40+ GB |
| SSH Access |
Required with sudo privileges |
Key-based authentication |
sudo apt-get update
sudo apt-get install -y ansible python3-pip
pip3 install docker docker-compose
sudo dnf install -y ansible python3-pip
pip3 install docker docker-compose
brew install ansible
pip3 install docker docker-compose
# inventory/production
[mixpost]
mixpost.example.com ansible_host=192.168.1.100
[mixpost:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
# inventory/production
[mixpost]
mixpost-prod-01 ansible_host=192.168.1.100
mixpost-prod-02 ansible_host=192.168.1.101
[mixpost_staging]
mixpost-staging ansible_host=192.168.1.110
[all:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
# Production-specific variables
[mixpost:vars]
mixpost_environment=production
mixpost_app_url=https://mixpost.example.com
mixpost_ssl_enabled=true
# Staging-specific variables
[mixpost_staging:vars]
mixpost_environment=staging
mixpost_app_url=https://mixpost-staging.example.com
mixpost_ssl_enabled=true
| Variable |
Description |
Example |
mixpost_app_url |
Public URL for Mixpost |
https://mixpost.example.com |
mixpost_app_name |
Application name |
Mixpost |
mixpost_app_env |
Environment |
production |
mixpost_db_password |
MySQL password |
(generate secure) |
mixpost_redis_password |
Redis password |
(generate secure) |
mixpost_app_key |
Laravel APP_KEY |
base64:... |
| Variable |
Description |
Default |
mixpost_version |
Mixpost Docker image tag |
2.4.0 |
mixpost_app_port |
Internal container port |
8080 |
mixpost_host_port |
Host port mapping |
8080 |
mixpost_install_dir |
Installation directory |
/opt/mixpost |
mixpost_ssl_enabled |
Enable SSL/TLS |
true |
mixpost_ssl_email |
Let’s Encrypt email |
admin@example.com |
mixpost_backup_enabled |
Enable automated backups |
true |
mixpost_monitoring_enabled |
Enable monitoring |
false |
# Generate MySQL password
openssl rand -base64 32
# Generate Redis password
openssl rand -base64 24
# Generate APP_KEY
docker run --rm php:8.2-cli php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
mixpost-ansible/
├── ansible.cfg
├── inventory/
│ └── production
├── group_vars/
│ └── all.yml
├── host_vars/
│ └── mixpost.example.com.yml
├── playbooks/
│ ├── deploy.yml
│ ├── security.yml
│ └── backup.yml
├── roles/
│ ├── docker/
│ ├── mixpost/
│ ├── nginx/
│ └── security/
└── templates/
├── docker-compose.yml.j2
├── .env.j2
└── nginx.conf.j2
# ansible.cfg
[defaults]
inventory = ./inventory/production
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callback_whitelist = profile_tasks
[ssh_connection]
pipelining = True
control_path = /tmp/ansible-%%h-%%p-%%r
# group_vars/all.yml
---
# Mixpost Configuration
mixpost_version: "2.4.0"
mixpost_app_name: "Mixpost"
mixpost_app_env: production
mixpost_app_debug: false
mixpost_app_timezone: UTC
mixpost_app_locale: en
# Network Configuration
mixpost_app_port: 8080
mixpost_host_port: 8080
# Installation Path
mixpost_install_dir: /opt/mixpost
# Database Configuration
mixpost_db_name: mixpost
mixpost_db_user: mixpost
mixpost_db_password: "{{ lookup('env', 'MIXPOST_DB_PASSWORD') | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}"
mixpost_db_root_password: "{{ lookup('env', 'MIXPOST_DB_ROOT_PASSWORD') | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}"
# Redis Configuration
mixpost_redis_password: "{{ lookup('env', 'MIXPOST_REDIS_PASSWORD') | default(lookup('password', '/dev/null length=24 chars=ascii_letters,digits'), true) }}"
# APP Key (generate once and store securely)
mixpost_app_key: "{{ lookup('env', 'MIXPOST_APP_KEY') | default('base64:' + lookup('password', '/dev/null length=32 chars=ascii_letters,digits'), true) }}"
# SSL/TLS Configuration
mixpost_ssl_enabled: true
mixpost_ssl_email: "admin@example.com"
mixpost_ssl_provider: letsencrypt
# Backup Configuration
mixpost_backup_enabled: true
mixpost_backup_retention_days: 30
mixpost_backup_dir: /backup/mixpost
# Monitoring Configuration
mixpost_monitoring_enabled: false
mixpost_health_check_path: /api/health
# Firewall Configuration
firewall_enabled: true
firewall_ssh_port: 22
firewall_http_port: 80
firewall_https_port: 443
# User Configuration
deploy_user: deploy
deploy_user_ssh_key: "~/.ssh/id_ed25519.pub"
# playbooks/deploy.yml
---
- name: Deploy Mixpost
hosts: mixpost
become: true
gather_facts: true
vars:
app_root: "{{ mixpost_install_dir }}"
app_port: "{{ mixpost_app_port }}"
pre_tasks:
- name: Validate required variables
ansible.builtin.assert:
that:
- mixpost_app_url is defined
- mixpost_app_url | length > 0
fail_msg: "mixpost_app_url is required"
- name: Update apt cache (Debian/Ubuntu)
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Update dnf cache (RHEL)
ansible.builtin.dnf:
update_cache: true
when: ansible_os_family == "RedHat"
roles:
- role: docker
tags: ['docker', 'prerequisites']
- role: mixpost
tags: ['mixpost', 'application']
- role: nginx
tags: ['nginx', 'reverse-proxy']
- role: security
tags: ['security', 'hardening']
tasks:
- name: Create application directory
ansible.builtin.file:
path: "{{ app_root }}"
state: directory
mode: '0755'
owner: root
group: root
- name: Create volume directories
ansible.builtin.file:
path: "{{ app_root }}/{{ item }}"
state: directory
mode: '0755'
owner: 33
group: 33
loop:
- storage/app
- storage/logs
- db
- redis
- name: Generate APP_KEY if not provided
ansible.builtin.set_fact:
mixpost_app_key: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits') }}"
when: mixpost_app_key is not defined or mixpost_app_key | length == 0
no_log: true
- name: Write Docker Compose file
ansible.builtin.template:
src: templates/docker-compose.yml.j2
dest: "{{ app_root }}/docker-compose.yml"
mode: '0644'
vars:
app_key: "{{ mixpost_app_key }}"
db_password: "{{ mixpost_db_password }}"
db_root_password: "{{ mixpost_db_root_password }}"
redis_password: "{{ mixpost_redis_password }}"
- name: Write environment file
ansible.builtin.template:
src: templates/.env.j2
dest: "{{ app_root }}/.env"
mode: '0600'
vars:
app_key: "{{ mixpost_app_key }}"
db_password: "{{ mixpost_db_password }}"
redis_password: "{{ mixpost_redis_password }}"
no_log: true
- name: Start Mixpost stack
community.docker.docker_compose:
project_src: "{{ app_root }}"
state: present
restart: true
- name: Wait for Mixpost to be healthy
ansible.builtin.uri:
url: "http://127.0.0.1:{{ app_port }}/api/health"
method: GET
status_code: 200
register: health_check
retries: 30
delay: 10
until: health_check.status == 200
- name: Display access information
ansible.builtin.debug:
msg: |
Mixpost deployment completed!
URL: {{ mixpost_app_url }}
Default credentials:
Email: admin@example.com
Password: changeme
IMPORTANT: Change default credentials immediately!
post_tasks:
- name: Verify services are running
community.docker.docker_container_info:
name: "{{ item }}"
loop:
- mixpost
- mixpost-mysql
- mixpost-redis
register: container_status
- name: Display container status
ansible.builtin.debug:
var: container_status.results | map(attribute='container.State.Status') | list
# templates/docker-compose.yml.j2
version: '3.8'
services:
mixpost:
image: inovector/mixpost:{{ mixpost_version }}
container_name: mixpost
user: "33:33"
environment:
APP_NAME: {{ mixpost_app_name }}
APP_ENV: {{ mixpost_app_env }}
APP_KEY: {{ app_key }}
APP_URL: {{ mixpost_app_url }}
APP_DEBUG: "{{ mixpost_app_debug }}"
APP_TIMEZONE: {{ mixpost_app_timezone }}
DB_HOST: mixpost-mysql
DB_PORT: 3306
DB_DATABASE: {{ mixpost_db_name }}
DB_USERNAME: {{ mixpost_db_user }}
DB_PASSWORD: {{ db_password }}
REDIS_HOST: mixpost-redis
REDIS_PORT: 6379
REDIS_PASSWORD: {{ redis_password }}
QUEUE_CONNECTION: redis
FILESYSTEM_DISK: local
ports:
- "127.0.0.1:{{ app_port }}:80"
volumes:
- {{ app_root }}/storage/app:/var/www/html/storage/app:rw
- {{ app_root }}/storage/logs:/var/www/html/storage/logs:rw
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- mixpost-internal
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
mysql:
image: mysql/mysql-server:8.0
container_name: mixpost-mysql
user: "999:999"
environment:
MYSQL_DATABASE: {{ mixpost_db_name }}
MYSQL_USER: {{ mixpost_db_user }}
MYSQL_PASSWORD: {{ db_password }}
MYSQL_ROOT_PASSWORD: {{ db_root_password }}
volumes:
- {{ app_root }}/db:/var/lib/mysql:rw
networks:
- mixpost-internal
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p{{ db_root_password }}"]
interval: 20s
timeout: 10s
retries: 5
start_period: 30s
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
redis:
image: redis:7-alpine
container_name: mixpost-redis
user: "999:999"
command: redis-server --appendonly yes --requirepass {{ redis_password }}
volumes:
- {{ app_root }}/redis:/data:rw
networks:
- mixpost-internal
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "{{ redis_password }}", "ping"]
interval: 20s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
networks:
mixpost-internal:
driver: bridge
internal: true
# templates/.env.j2
# =============================================================================
# MIXPOST ENVIRONMENT CONFIGURATION
# Generated by Ansible - Do not edit manually
# =============================================================================
# Application
APP_NAME={{ mixpost_app_name }}
APP_ENV={{ mixpost_app_env }}
APP_KEY={{ app_key }}
APP_URL={{ mixpost_app_url }}
APP_DEBUG={{ mixpost_app_debug }}
APP_TIMEZONE={{ mixpost_app_timezone }}
APP_LOCALE={{ mixpost_app_locale }}
APP_FALLBACK_LOCALE=en
# Database
DB_CONNECTION=mysql
DB_HOST=mixpost-mysql
DB_PORT=3306
DB_DATABASE={{ mixpost_db_name }}
DB_USERNAME={{ mixpost_db_user }}
DB_PASSWORD={{ db_password }}
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
# Redis
REDIS_HOST=mixpost-redis
REDIS_PORT=6379
REDIS_PASSWORD={{ redis_password }}
REDIS_DB=0
# Queue
QUEUE_CONNECTION=redis
QUEUE_RETRY_AFTER=900
QUEUE_MAX_ATTEMPTS=3
# File Storage
FILESYSTEM_DISK=local
# Session
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
# Cache
CACHE_DRIVER=redis
CACHE_PREFIX=mixpost
# Logging
LOG_CHANNEL=stack
LOG_LEVEL=info
LOG_DAILY_DAYS=7
# Security
TRUSTED_PROXIES=*
¶ roles/security/tasks/main.yml
# roles/security/tasks/main.yml
---
- name: Configure UFW firewall
ansible.builtin.ufw:
direction: incoming
policy: deny
when: firewall_enabled and ansible_os_family == "Debian"
- name: Allow SSH through firewall
ansible.builtin.ufw:
rule: limit
port: "{{ firewall_ssh_port }}"
proto: tcp
when: firewall_enabled and ansible_os_family == "Debian"
- name: Allow HTTP through firewall
ansible.builtin.ufw:
rule: allow
port: "{{ firewall_http_port }}"
proto: tcp
when: firewall_enabled and ansible_os_family == "Debian"
- name: Allow HTTPS through firewall
ansible.builtin.ufw:
rule: allow
port: "{{ firewall_https_port }}"
proto: tcp
when: firewall_enabled and ansible_os_family == "Debian"
- name: Enable UFW
ansible.builtin.ufw:
state: enabled
when: firewall_enabled and ansible_os_family == "Debian"
- name: Configure firewalld (RHEL)
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop:
- ssh
- http
- https
when: firewall_enabled and ansible_os_family == "RedHat"
- name: Install fail2ban
ansible.builtin.package:
name: fail2ban
state: present
when: ansible_os_family == "Debian"
- name: Configure fail2ban for Mixpost
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.d/mixpost.local
mode: '0644'
notify: restart fail2ban
- name: Enable and start fail2ban
ansible.builtin.service:
name: fail2ban
enabled: true
state: started
- name: Configure automatic security updates
ansible.builtin.template:
src: unattended-upgrades.j2
dest: /etc/apt/apt.conf.d/50unattended-upgrades
mode: '0644'
when: ansible_os_family == "Debian"
- name: Enable unattended upgrades
ansible.builtin.debconf:
name: unattended-upgrades
question: unattended-upgrades/enable_auto_updates
value: "true"
vtype: boolean
when: ansible_os_family == "Debian"
- name: Configure sysctl hardening
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: true
loop:
- { name: 'net.ipv4.ip_forward', value: '0' }
- { name: 'net.ipv4.conf.all.send_redirects', value: '0' }
- { name: 'net.ipv4.conf.default.send_redirects', value: '0' }
- { name: 'net.ipv4.conf.all.accept_source_route', value: '0' }
- { name: 'net.ipv4.conf.default.accept_source_route', value: '0' }
- { name: 'net.ipv4.conf.all.accept_redirects', value: '0' }
- { name: 'net.ipv4.conf.default.accept_redirects', value: '0' }
- { name: 'net.ipv4.conf.all.secure_redirects', value: '0' }
- { name: 'net.ipv4.conf.default.secure_redirects', value: '0' }
- { name: 'net.ipv4.conf.all.log_martians', value: '1' }
- { name: 'net.ipv4.conf.default.log_martians', value: '1' }
- { name: 'net.ipv4.icmp_echo_ignore_broadcasts', value: '1' }
- { name: 'net.ipv4.icmp_ignore_bogus_error_responses', value: '1' }
- { name: 'net.ipv4.conf.all.rp_filter', value: '1' }
- { name: 'net.ipv4.conf.default.rp_filter', value: '1' }
- { name: 'net.ipv4.tcp_syncookies', value: '1' }
¶ Generate Passwords and Set Environment
# Export secure passwords
export MIXPOST_DB_PASSWORD=$(openssl rand -base64 32)
export MIXPOST_DB_ROOT_PASSWORD=$(openssl rand -base64 32)
export MIXPOST_REDIS_PASSWORD=$(openssl rand -base64 24)
export MIXPOST_APP_KEY=$(docker run --rm php:8.2-cli php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;")
# Verify variables
echo "DB Password: $MIXPOST_DB_PASSWORD"
echo "APP Key: $MIXPOST_APP_KEY"
# Test connection
ansible all -m ping
# Run deployment
ansible-playbook playbooks/deploy.yml
# Run with specific tags
ansible-playbook playbooks/deploy.yml --tags 'docker,mixpost'
# Run with verbose output
ansible-playbook playbooks/deploy.yml -vv
# Dry run (check mode)
ansible-playbook playbooks/deploy.yml --check
# Production
ansible-playbook -i inventory/production playbooks/deploy.yml
# Staging
ansible-playbook -i inventory/staging playbooks/deploy.yml
# Development
ansible-playbook -i inventory/development playbooks/deploy.yml
# Check container status
ansible all -m shell -a "docker compose ps" -o
# Check application health
curl https://mixpost.example.com/api/health
# View logs
ansible all -m shell -a "docker compose logs mixpost"
# playbooks/backup.yml
---
- name: Backup Mixpost
hosts: mixpost
become: true
tasks:
- name: Create backup directory
ansible.builtin.file:
path: "{{ mixpost_backup_dir }}"
state: directory
mode: '0700'
- name: Backup database
ansible.builtin.shell: |
docker compose exec -T mysql mysqldump -u root -p{{ mixpost_db_root_password }} \
--single-transaction \
{{ mixpost_db_name }} | gzip > {{ mixpost_backup_dir }}/db-$(date +%Y%m%d_%H%M%S).sql.gz
args:
chdir: "{{ mixpost_install_dir }}"
- name: Backup storage
ansible.builtin.archive:
path: "{{ mixpost_install_dir }}/storage/app"
dest: "{{ mixpost_backup_dir }}/storage-$(date +%Y%m%d_%H%M%S).tar.gz"
format: gz
- name: Remove old backups
ansible.builtin.find:
paths: "{{ mixpost_backup_dir }}"
age: "{{ mixpost_backup_retention_days }}d"
register: old_backups
- name: Delete old backups
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_backups.files }}"
ansible-playbook playbooks/backup.yml
| Issue |
Solution |
| SSH connection failed |
Verify SSH key permissions: chmod 600 ~/.ssh/id_ed25519 |
| Docker not found |
Run playbook with --tags 'docker' to install Docker |
| Port already in use |
Change mixpost_host_port in variables |
| SSL certificate failed |
Verify DNS points to server; check mixpost_ssl_email |
| Container won’t start |
Check logs: docker compose logs mixpost |
# Run with increased verbosity
ansible-playbook playbooks/deploy.yml -vvv
# Step through tasks
ansible-playbook playbooks/deploy.yml --step
# Start at specific task
ansible-playbook playbooks/deploy.yml --start-at-task="Start Mixpost stack"
# Gather facts
ansible all -m setup
# Check disk space
ansible all -m shell -a "df -h"
# Check memory
ansible all -m shell -a "free -m"
# Check Docker status
ansible all -m shell -a "systemctl status docker"
Any questions?
Feel free to contact us. Find all contact information on our contact page.