This guide provides Ansible automation for deploying Socioboard with Docker Compose on Debian, Ubuntu, and RHEL-compatible systems.
⚠️ Important: Socioboard 5.0’s last release was in November 2019. For new deployments, consider actively maintained alternatives like Mixpost or Postiz.
The Ansible playbook automates:
| Component | Minimum | Recommended |
|---|---|---|
| OS | Debian 10+, Ubuntu 20.04+, RHEL 9+ | Debian 12, Ubuntu 24.04 |
| CPU | 2 cores | 4 cores |
| RAM | 4 GB | 8 GB |
| Storage | 20 GB | 40 GB SSD |
| Network | Public IP, domain configured | Static IP, DNS configured |
# Generate SSH key (if needed)
ssh-keygen -t ed25519 -C "ansible@socioboard"
# Copy to target host
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip
Create inventory.ini:
[socioboard]
socioboard-prod ansible_host=203.0.113.50 ansible_user=deploy
[socioboard:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=~/.ssh/id_ed25519
[all:vars]
# Security settings
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
Create socioboard.yml:
---
- name: Deploy Socioboard with Docker Compose
hosts: socioboard
become: true
gather_facts: true
vars:
# Application settings
app_name: socioboard
app_root: /opt/socioboard
app_domain: "socioboard.example.com"
app_email: "admin@example.com"
# Docker settings
docker_compose_version: "v2.24.0"
# Database credentials (generate secure passwords!)
db_password: "{{ lookup('password', '/dev/urandom chars=ascii_letters,digits length=32') }}"
mysql_root_password: "{{ lookup('password', '/dev/urandom chars=ascii_letters,digits length=32') }}"
mongo_root_user: "mongoroot"
mongo_root_password: "{{ lookup('password', '/dev/urandom chars=ascii_letters,digits length=32') }}"
mongo_password: "{{ lookup('password', '/dev/urandom chars=ascii_letters,digits length=32') }}"
# Admin credentials (CHANGE AFTER FIRST LOGIN!)
admin_email: "admin@scb.example.com"
admin_password: "ChangeMe123!SecurePassword"
# SSL/TLS settings
ssl_enabled: true
ssl_email: "{{ app_email }}"
# Firewall settings
firewall_enabled: true
firewall_allowed_tcp_ports:
- "22"
- "80"
- "443"
# Backup settings
backup_enabled: true
backup_retention_days: 7
backup_directory: "/var/backups/socioboard"
roles:
- role: geerlingguy.docker
tags: ['docker']
- role: geerlingguy.pip
tags: ['docker']
- role: geerlingguy.security
tags: ['security']
tasks:
# ============================================
# System Preparation
# ============================================
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install required packages
package:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- software-properties-common
- git
- ufw
- fail2ban
- certbot
- python3-certbot-nginx
state: present
when: ansible_os_family == "Debian"
- name: Install required packages (RHEL)
dnf:
name:
- dnf-utils
- curl
- git
- firewalld
- fail2ban
state: present
when: ansible_os_family == "RedHat"
# ============================================
# Docker Installation
# ============================================
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
state: present
when: ansible_os_family == "Debian"
- name: Add Docker repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
state: present
when: ansible_os_family == "Debian"
- name: Install Docker CE
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Enable and start Docker
service:
name: docker
state: started
enabled: yes
- name: Add user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
# ============================================
# Application Setup
# ============================================
- name: Create application directory
file:
path: "{{ app_root }}"
state: directory
mode: '0755'
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
- name: Create backup directory
file:
path: "{{ backup_directory }}"
state: directory
mode: '0750'
owner: root
group: docker
- name: Clone Socioboard repository
git:
repo: 'https://github.com/socioboard/Socioboard-5.0.git'
dest: "{{ app_root }}/src"
version: master
become_user: "{{ ansible_user }}"
# ============================================
# Docker Compose Configuration
# ============================================
- name: Create Docker Compose file
copy:
dest: "{{ app_root }}/docker-compose.yml"
mode: '0644'
content: |
services:
socioboard:
image: ghcr.io/socioboard/socioboard:5.0
container_name: socioboard-app
restart: unless-stopped
depends_on:
- mysql
- mongodb
networks:
- socioboard-internal
expose:
- "8080"
environment:
- NODE_ENV=production
- APP_ENV=production
- APP_URL=https://{{ app_domain }}
- DB_HOST=mysql
- DB_PORT=3306
- DB_NAME=socioboard
- DB_USER=socioboard
- DB_PASSWORD={{ db_password }}
- MONGO_HOST=mongodb
- MONGO_PORT=27017
- MONGO_DB=socioboard
- MONGO_USER=socioboard
- MONGO_PASSWORD={{ mongo_password }}
volumes:
- socioboard-data:/var/www/html/storage
- socioboard-logs:/var/log/socioboard
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=512m
- /run:noexec,nosuid,size=64m
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
mysql:
image: mysql:8.0
container_name: socioboard-mysql
restart: unless-stopped
networks:
- socioboard-internal
expose:
- "3306"
environment:
- MYSQL_ROOT_PASSWORD={{ mysql_root_password }}
- MYSQL_DATABASE=socioboard
- MYSQL_USER=socioboard
- MYSQL_PASSWORD={{ db_password }}
volumes:
- mysql-data:/var/lib/mysql
- mysql-backups:/backups
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
- SYS_NICE
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=256m
- /run:noexec,nosuid,size=64m
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
mongodb:
image: mongo:6.0
container_name: socioboard-mongodb
restart: unless-stopped
networks:
- socioboard-internal
expose:
- "27017"
environment:
- MONGO_INITDB_ROOT_USERNAME={{ mongo_root_user }}
- MONGO_INITDB_ROOT_PASSWORD={{ mongo_root_password }}
- MONGO_INITDB_DATABASE=socioboard
volumes:
- mongodb-data:/data/db
- mongodb-backups:/backups
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
- SYS_NICE
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=256m
- /run:noexec,nosuid,size=64m
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
nginx:
image: nginx:alpine
container_name: socioboard-nginx
restart: unless-stopped
depends_on:
- socioboard
networks:
- socioboard-internal
- socioboard-web
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
- nginx-cache:/var/cache/nginx
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=128m
- /run:noexec,nosuid,size=64m
- /var/cache/nginx:noexec,nosuid,size=512m
networks:
socioboard-internal:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
socioboard-web:
driver: bridge
volumes:
socioboard-data:
socioboard-logs:
mysql-data:
mysql-backups:
mongodb-data:
mongodb-backups:
nginx-cache:
- name: Create Nginx directory structure
file:
path: "{{ app_root }}/{{ item }}"
state: directory
mode: '0755'
owner: "{{ ansible_user }}"
loop:
- nginx
- nginx/conf.d
- ssl
- name: Create Nginx main configuration
copy:
dest: "{{ app_root }}/nginx/nginx.conf"
mode: '0644'
content: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
include /etc/nginx/conf.d/*.conf;
}
- name: Create Nginx site configuration
copy:
dest: "{{ app_root }}/nginx/conf.d/socioboard.conf"
mode: '0644'
content: |
server {
listen 80;
server_name {{ app_domain }};
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name {{ app_domain }};
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
limit_req zone=general burst=20 nodelay;
limit_req zone=api burst=10 nodelay;
location / {
proxy_pass http://socioboard:8080;
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location ~ /\. {
deny all;
}
location ~ /(\.git|\.env|composer\.(json|lock)) {
deny all;
return 404;
}
}
- name: Create environment file
copy:
dest: "{{ app_root }}/.env"
mode: '0600'
content: |
# Database Credentials
DB_PASSWORD={{ db_password }}
MYSQL_ROOT_PASSWORD={{ mysql_root_password }}
MONGO_ROOT_USER={{ mongo_root_user }}
MONGO_ROOT_PASSWORD={{ mongo_root_password }}
MONGO_PASSWORD={{ mongo_password }}
# Application Settings
NODE_ENV=production
APP_ENV=production
APP_URL=https://{{ app_domain }}
APP_DEBUG=false
# Admin Credentials
ADMIN_EMAIL={{ admin_email }}
ADMIN_PASSWORD={{ admin_password }}
# Ports
HTTP_PORT=80
HTTPS_PORT=443
# ============================================
# SSL/TLS Certificate
# ============================================
- name: Generate SSL certificate with Let's Encrypt
shell: |
certbot certonly --standalone \
--email {{ ssl_email }} \
--agree-tos \
--non-interactive \
-d {{ app_domain }}
args:
creates: /etc/letsencrypt/live/{{ app_domain }}/fullchain.pem
- name: Copy SSL certificates
copy:
src: "{{ item.src }}"
dest: "{{ app_root }}/ssl/{{ item.dest }}"
mode: '0644'
owner: "{{ ansible_user }}"
loop:
- src: "/etc/letsencrypt/live/{{ app_domain }}/fullchain.pem"
dest: "fullchain.pem"
- src: "/etc/letsencrypt/live/{{ app_domain }}/privkey.pem"
dest: "privkey.pem"
# ============================================
# Firewall Configuration
# ============================================
- name: Configure UFW firewall
when: firewall_enabled and ansible_os_family == "Debian"
block:
- name: Enable UFW logging
ufw:
logging: 'on'
- name: Allow SSH
ufw:
rule: allow
port: '22'
proto: tcp
- name: Allow HTTP
ufw:
rule: allow
port: '80'
proto: tcp
- name: Allow HTTPS
ufw:
rule: allow
port: '443'
proto: tcp
- name: Enable UFW
ufw:
state: enabled
policy: deny
- name: Configure firewalld (RHEL)
when: firewall_enabled and ansible_os_family == "RedHat"
block:
- name: Install firewalld
dnf:
name: firewalld
state: present
- name: Enable and start firewalld
service:
name: firewalld
state: started
enabled: yes
- name: Allow SSH
firewalld:
service: ssh
permanent: true
state: enabled
- name: Allow HTTP
firewalld:
service: http
permanent: true
state: enabled
- name: Allow HTTPS
firewalld:
service: https
permanent: true
state: enabled
- name: Reload firewalld
service:
name: firewalld
state: reloaded
# ============================================
# Fail2ban Configuration
# ============================================
- name: Configure Fail2ban
when: ansible_os_family == "Debian"
block:
- name: Create Fail2ban jail for Nginx
copy:
dest: /etc/fail2ban/jail.d/nginx.conf
mode: '0644'
content: |
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/*error.log
maxretry = 3
bantime = 3600
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/*error.log
maxretry = 5
bantime = 3600
- name: Enable and start Fail2ban
service:
name: fail2ban
state: started
enabled: yes
# ============================================
# Deploy Application
# ============================================
- name: Start Socioboard with Docker Compose
command: docker compose up -d
args:
chdir: "{{ app_root }}"
become_user: "{{ ansible_user }}"
- name: Wait for application to be ready
wait_for:
port: 443
host: "{{ app_domain }}"
delay: 10
timeout: 120
state: started
# ============================================
# Backup Script
# ============================================
- name: Create backup script
when: backup_enabled
copy:
dest: "{{ app_root }}/backup.sh"
mode: '0755'
content: |
#!/bin/bash
set -e
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="{{ backup_directory }}"
echo "Starting backup at $DATE"
# MySQL backup
docker exec socioboard-mysql mysqldump \
-u root -p{{ mysql_root_password }} \
socioboard > "$BACKUP_DIR/mysql_backup_$DATE.sql"
# MongoDB backup
docker exec socioboard-mongodb mongodump \
--db socioboard \
--out "$BACKUP_DIR/mongo_backup_$DATE"
# Compress backups
gzip "$BACKUP_DIR/mysql_backup_$DATE.sql"
tar -czf "$BACKUP_DIR/mongo_backup_$DATE.tar.gz" \
-C "$BACKUP_DIR/mongo_backup_$DATE" .
rm -rf "$BACKUP_DIR/mongo_backup_$DATE"
# Cleanup old backups
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +{{ backup_retention_days }} -delete
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +{{ backup_retention_days }} -delete
echo "Backup completed successfully"
- name: Create backup cron job
when: backup_enabled
cron:
name: "Socioboard daily backup"
minute: "0"
hour: "2"
job: "{{ app_root }}/backup.sh >> /var/log/socioboard-backup.log 2>&1"
# ============================================
# Final Output
# ============================================
- name: Display deployment summary
debug:
msg: |
============================================
Socioboard Deployment Complete!
============================================
Application URL: https://{{ app_domain }}
Admin Panel: https://{{ app_domain }}/admin
Admin Credentials:
Email: {{ admin_email }}
Password: {{ admin_password }}
⚠️ IMPORTANT: Change admin password after first login!
Database Credentials (stored in {{ app_root }}/.env):
MySQL Password: {{ db_password }}
MongoDB User: {{ mongo_root_user }}
MongoDB Password: {{ mongo_root_password }}
Backup Location: {{ backup_directory }}
Useful Commands:
cd {{ app_root }}
docker compose ps # Check status
docker compose logs -f # View logs
docker compose down # Stop services
./backup.sh # Manual backup
============================================
Create requirements.yml:
---
collections: []
roles:
- name: geerlingguy.docker
version: "6.1.0"
- name: geerlingguy.pip
version: "2.2.0"
- name: geerlingguy.security
version: "2.1.2"
Install requirements:
ansible-galaxy install -r requirements.yml
# Install Ansible
pip install ansible
# Or on Debian/Ubuntu
sudo apt-get install -y ansible
# Verify installation
ansible --version
mkdir -p socioboard-deploy
cd socioboard-deploy
# Create files
touch inventory.ini socioboard.yml requirements.yml
ansible-galaxy install -r requirements.yml
Edit inventory.ini with your server details.
# Test connection
ansible -i inventory.ini -m ping all
# Run playbook
ansible-playbook -i inventory.ini socioboard.yml
# Run with specific tags
ansible-playbook -i inventory.ini socioboard.yml --tags docker
ansible-playbook -i inventory.ini socioboard.yml --tags security
# SSH to server
ssh user@your-server-ip
# Check Docker containers
docker compose ps
# Check application health
curl -k https://localhost/health
# Check logs
docker compose logs -f socioboard
ansible-playbook -i inventory.ini socioboard.yml --tags update
cd /opt/socioboard
git pull
docker compose pull
docker compose up -d --force-recreate
# Manual backup
/opt/socioboard/backup.sh
# Restore MySQL
docker exec -i socioboard-mysql mysql -u root -p < backup.sql
# Restore MongoDB
docker exec socioboard-mongodb mongorestore /backups/mongo_backup
The playbook includes:
# Test SSH
ansible -i inventory.ini -m ping all
# Verbose output
ansible-playbook -i inventory.ini socioboard.yml -vvv
# Check Docker status
systemctl status docker
# Verify Docker Compose
docker compose version
# Check container logs
docker compose logs -f
# Renew certificate
certbot renew
# Check certificate
openssl x509 -in /etc/letsencrypt/live/socioboard.example.com/fullchain.pem -text -noout
Any questions?
Feel free to contact us. Find all contact information on our contact page.