Complete Docker Compose deployment guide for Mixpost v2.4.0. This guide covers minimal quick-start configuration, production-ready hardened deployment, container security, networking, and operational best practices for Linux DevOps teams.
Current Version: v2.4.0
Docker Minimum: 24.0+
Docker Compose: 2.20+
Estimated Setup: 15-20 minutes
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores | 4 cores |
| RAM | 2 GB | 4 GB |
| Disk | 20 GB | 40+ GB SSD |
| OS | Linux (any distro) | Debian 12, Ubuntu 22.04, RHEL 9 |
| Component | Version | Purpose |
|---|---|---|
| Docker | 24.0+ | Container runtime |
| Docker Compose | 2.20+ | Container orchestration |
| Git | 2.30+ | Version control |
# Update package cache
sudo apt-get update
# Install prerequisites
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Set up repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Verify installation
docker --version
docker compose version
# Add user to docker group (optional, for non-root docker access)
sudo usermod -aG docker $USER
newgrp docker
# Add Docker repository
sudo dnf install -y dnf-utils
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Install Docker
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Start and enable Docker
sudo systemctl start docker
sudo systemctl enable docker
sudo systemctl status docker
# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker
# Create application directory
mkdir -p ~/mixpost
cd ~/mixpost
# Create volume directories
mkdir -p storage/app storage/logs db redis
# docker-compose.yml
version: '3.8'
services:
mixpost:
image: inovector/mixpost:2.4.0
container_name: mixpost
ports:
- "8080:80"
environment:
APP_NAME: Mixpost
APP_URL: http://localhost:8080
DB_HOST: mysql
DB_DATABASE: mixpost
DB_USERNAME: mixpost
DB_PASSWORD: mixpost123
REDIS_HOST: redis
volumes:
- ./storage/app:/var/www/html/storage/app
- ./storage/logs:/var/www/html/storage/logs
depends_on:
- mysql
- redis
mysql:
image: mysql/mysql-server:8.0
container_name: mixpost-mysql
environment:
MYSQL_DATABASE: mixpost
MYSQL_USER: mixpost
MYSQL_PASSWORD: mixpost123
MYSQL_ROOT_PASSWORD: rootpass123
volumes:
- ./db:/var/lib/mysql
redis:
image: redis:7-alpine
container_name: mixpost-redis
volumes:
- ./redis:/data
networks:
default:
driver: bridge
# Start all services
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f
http://localhost:8080admin@example.comchangeme⚠️ Warning: This minimal configuration is NOT secure for production. Use only for testing!
/opt/mixpost/
├── docker-compose.yml
├── docker-compose.prod.yml
├── .env
├── .env.production
├── nginx/
│ └── conf.d/
│ └── mixpost.conf
├── ssl/
│ ├── cert.pem
│ └── key.pem
├── storage/
│ ├── app/
│ └── logs/
├── db/
└── redis/
# docker-compose.prod.yml
version: '3.8'
services:
mixpost:
image: inovector/mixpost:2.4.0
container_name: mixpost
# Run as non-root user
user: "33:33"
# Security options
security_opt:
- no-new-privileges:true
# Drop all capabilities, add only required
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
# Read-only root filesystem
read_only: true
# Temporary filesystems
tmpfs:
- /tmp:noexec,nosuid,size=100m
- /var/run:noexec,nosuid,size=10m
environment:
APP_NAME: ${APP_NAME:-Mixpost}
APP_ENV: production
APP_KEY: ${APP_KEY}
APP_URL: ${APP_URL}
APP_DEBUG: "false"
APP_TIMEZONE: ${APP_TIMEZONE:-UTC}
DB_HOST: mixpost-mysql
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE:-mixpost}
DB_USERNAME: ${DB_USERNAME:-mixpost}
DB_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: mixpost-redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
QUEUE_CONNECTION: redis
FILESYSTEM_DISK: local
SESSION_SECURE_COOKIE: "true"
SESSION_HTTP_ONLY: "true"
SESSION_SAME_SITE: lax
ports:
- "127.0.0.1:${APP_PORT:-8080}:80"
volumes:
- ./storage/app:/var/www/html/storage/app:rw
- ./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
reservations:
cpus: '0.25'
memory: 128M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
mysql:
image: mysql/mysql-server:8.0
container_name: mixpost-mysql
# Run as non-root user
user: "999:999"
# Security options
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Read-only root filesystem
read_only: true
# Temporary filesystems
tmpfs:
- /tmp:noexec,nosuid,size=50m
- /var/run:noexec,nosuid,size=10m
environment:
MYSQL_DATABASE: ${DB_DATABASE:-mixpost}
MYSQL_USER: ${DB_USERNAME:-mixpost}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
# MySQL performance tuning
MYSQL_ROOT_HOST: "%"
volumes:
- ./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
reservations:
cpus: '0.25'
memory: 256M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
redis:
image: redis:7-alpine
container_name: mixpost-redis
# Run as non-root user
user: "999:999"
# Security options
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
# Read-only root filesystem
read_only: true
# Temporary filesystems
tmpfs:
- /tmp:noexec,nosuid,size=20m
command: >
redis-server
--appendonly yes
--requirepass ${REDIS_PASSWORD}
--maxmemory 128mb
--maxmemory-policy allkeys-lru
volumes:
- ./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
reservations:
cpus: '0.1'
memory: 64M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
mixpost-internal:
driver: bridge
internal: true
ipam:
config:
- subnet: 172.28.0.0/16
# .env.production
# =============================================================================
# MIXPOST PRODUCTION ENVIRONMENT
# Copy to .env and update values
# =============================================================================
# Application
APP_NAME=Mixpost
APP_URL=https://mixpost.example.com
APP_PORT=8080
APP_TIMEZONE=UTC
# Generate APP_KEY: docker run --rm php:8.2-cli php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
APP_KEY=base64:your-generated-32-character-key-here
# Database - Generate secure passwords
# openssl rand -base64 32
DB_DATABASE=mixpost
DB_USERNAME=mixpost
DB_PASSWORD=generate-secure-32-character-password-here
DB_ROOT_PASSWORD=generate-secure-32-character-root-password-here
# Redis - Generate secure password
# openssl rand -base64 24
REDIS_PASSWORD=generate-secure-24-character-password-here
# SSL/TLS (if using Let's Encrypt)
SSL_EMAIL=admin@example.com
SSL_CERT_PATH=/etc/letsencrypt/live/mixpost.example.com/fullchain.pem
SSL_KEY_PATH=/etc/letsencrypt/live/mixpost.example.com/privkey.pem
#!/bin/bash
# generate-passwords.sh
echo "=== Mixpost Password Generator ==="
echo ""
# Generate APP_KEY
APP_KEY=$(docker run --rm php:8.2-cli php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;")
echo "APP_KEY=$APP_KEY"
# Generate DB_PASSWORD
DB_PASSWORD=$(openssl rand -base64 32)
echo "DB_PASSWORD=$DB_PASSWORD"
# Generate DB_ROOT_PASSWORD
DB_ROOT_PASSWORD=$(openssl rand -base64 32)
echo "DB_ROOT_PASSWORD=$DB_ROOT_PASSWORD"
# Generate REDIS_PASSWORD
REDIS_PASSWORD=$(openssl rand -base64 24)
echo "REDIS_PASSWORD=$REDIS_PASSWORD"
echo ""
echo "Save these passwords securely!"
| Control | Status | Description |
|---|---|---|
| Non-root user | ✅ | Run containers as non-root (user: “33:33”) |
| Read-only filesystem | ✅ | Root filesystem is read-only |
| Capability drop | ✅ | Drop ALL, add only required |
| No new privileges | ✅ | Prevent privilege escalation |
| Resource limits | ✅ | CPU and memory limits configured |
| Health checks | ✅ | Container health monitoring |
| Network isolation | ✅ | Internal network for DB/Redis |
| Log rotation | ✅ | JSON log rotation configured |
| Secrets management | ⚠️ | Use Docker secrets or external vault |
| Image scanning | ⚠️ | Scan images for vulnerabilities |
# Install Docker Bench
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sudo sh docker-bench-security.sh
# Review output for Mixpost containers
# Install Trivy
sudo apt-get install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install -y trivy
# Scan Mixpost image
trivy image inovector/mixpost:2.4.0
# Scan with severity filter
trivy image --severity HIGH,CRITICAL inovector/mixpost:2.4.0
# /etc/nginx/sites-available/mixpost
upstream mixpost_backend {
server 127.0.0.1:8080;
keepalive 32;
}
# HTTP - Redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name mixpost.example.com;
# ACME challenge for Let's Encrypt
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirect all other HTTP to HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mixpost.example.com;
# SSL Certificate
ssl_certificate /etc/letsencrypt/live/mixpost.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mixpost.example.com/privkey.pem;
# Modern SSL configuration
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;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Security headers
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;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self';" always;
# Logging
access_log /var/log/nginx/mixpost-access.log;
error_log /var/log/nginx/mixpost-error.log;
# Client settings
client_max_body_size 50M;
client_body_buffer_size 128k;
client_body_timeout 60s;
# Proxy settings
location / {
proxy_pass http://mixpost_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
proxy_read_timeout 90s;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_buffering off;
}
# Health check endpoint (for monitoring)
location /api/health {
proxy_pass http://mixpost_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
access_log off;
}
}
# Enable site
sudo ln -s /etc/nginx/sites-available/mixpost /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Create certbot directory
sudo mkdir -p /var/www/certbot
# Obtain SSL certificate
sudo certbot certonly --webroot -w /var/www/certbot -d mixpost.example.com
# Reload Nginx
sudo systemctl reload nginx
# Auto-renewal
sudo crontab -e
# Add: 0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"
#!/bin/bash
# backup-mixpost-volumes.sh
set -e
BACKUP_DIR="/backup/mixpost"
DATE=$(date +%Y%m%d_%H%M%S)
MIXPOST_DIR="/opt/mixpost"
mkdir -p $BACKUP_DIR
echo "Starting Mixpost backup..."
# Backup database
echo "Backing up database..."
docker compose -C $MIXPOST_DIR exec -T mysql mysqldump -u root -p"$DB_ROOT_PASSWORD" \
--single-transaction \
--routines \
--triggers \
mixpost | gzip > $BACKUP_DIR/db-$DATE.sql.gz
# Backup storage
echo "Backing up storage..."
tar czf $BACKUP_DIR/storage-$DATE.tar.gz -C $MIXPOST_DIR storage/app
# Backup redis data (optional, for persistence)
echo "Backing up Redis data..."
tar czf $BACKUP_DIR/redis-$DATE.tar.gz -C $MIXPOST_DIR redis
# Compress database backup further
echo "Compressing backups..."
gzip -9 $BACKUP_DIR/storage-$DATE.tar.gz
gzip -9 $BACKUP_DIR/redis-$DATE.tar.gz
# Remove old backups (keep 30 days)
find $BACKUP_DIR -name "*.gz" -mtime +30 -delete
echo "Backup completed: $DATE"
echo "Backup location: $BACKUP_DIR"
#!/bin/bash
# restore-mixpost-volumes.sh
set -e
BACKUP_FILE="$1"
MIXPOST_DIR="/opt/mixpost"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup-file.sql.gz>"
exit 1
fi
echo "Starting Mixpost restore..."
# Stop application
echo "Stopping Mixpost..."
docker compose -C $MIXPOST_DIR down
# Restore database
echo "Restoring database..."
gunzip -c $BACKUP_FILE | docker compose -C $MIXPOST_DIR exec -T mysql mysql -u root -p"$DB_ROOT_PASSWORD" mixpost
# Start application
echo "Starting Mixpost..."
docker compose -C $MIXPOST_DIR up -d
echo "Restore completed!"
| Variable | Required | Default | Description |
|---|---|---|---|
APP_NAME |
✅ | Mixpost | Application name |
APP_ENV |
✅ | production | Environment mode |
APP_KEY |
✅ | - | 32-byte base64 encryption key |
APP_URL |
✅ | - | Public application URL |
APP_DEBUG |
✅ | false | Debug mode (disable in production) |
APP_TIMEZONE |
⚠️ | UTC | Default timezone |
APP_PORT |
⚠️ | 8080 | Internal container port |
DB_DATABASE |
✅ | mixpost | Database name |
DB_USERNAME |
✅ | mixpost | Database username |
DB_PASSWORD |
✅ | - | Database password |
DB_ROOT_PASSWORD |
✅ | - | MySQL root password |
REDIS_PASSWORD |
✅ | - | Redis password |
SSL_EMAIL |
⚠️ | - | Let’s Encrypt email |
# View running containers
docker compose ps
# View logs
docker compose logs -f mixpost
docker compose logs -f mixpost-mysql
docker compose logs -f mixpost-redis
# View specific log lines
docker compose logs --tail=100 mixpost
# Restart services
docker compose restart mixpost
docker compose restart
# Stop services
docker compose down
# Stop and remove volumes (dangerous!)
docker compose down -v
# Update to new version
docker compose pull
docker compose up -d
# Prune unused resources
docker system prune -af
# Check container health
docker inspect --format='{{.State.Health.Status}}' mixpost
# Check application health endpoint
curl http://127.0.0.1:8080/api/health
# Check database connectivity
docker compose exec mysql mysqladmin -u root -p status
# Check Redis connectivity
docker compose exec redis redis-cli -a password ping
# Monitor resource usage
docker stats mixpost mixpost-mysql mixpost-redis
# View container logs
docker compose logs --tail=1000 mixpost > mixpost.log
# Follow logs in real-time
docker compose logs -f mixpost | grep -i error
# Export all logs
docker compose logs > all-logs.txt
# Clear old logs
docker compose logs --tail=0
# Database backup
docker compose exec mysql mysqldump -u root -p mixpost > backup.sql
# Database restore
docker compose exec -T mysql mysql -u root -p mixpost < backup.sql
# Optimize tables
docker compose exec mysql mysql -u root -p -e "OPTIMIZE TABLE posts; OPTIMIZE TABLE social_accounts;"
# Check database size
docker compose exec mysql mysql -u root -p -e "SELECT table_schema, SUM(data_length + index_length) / 1024 / 1024 AS 'Size (MB)' FROM information_schema.tables WHERE table_schema='mixpost' GROUP BY table_schema;"
| Issue | Diagnosis | Solution |
|---|---|---|
| Container won’t start | docker compose logs mixpost |
Check APP_KEY format; verify dependencies |
| 502 Bad Gateway | Nginx logs; container status | Verify Mixpost container is running; check port mapping |
| Database connection error | docker compose logs mixpost-mysql |
Check health status; verify credentials |
| Queue not processing | docker compose logs mixpost |
Restart container; check Redis connection |
| High memory usage | docker stats |
Adjust resource limits; check for memory leaks |
| SSL certificate errors | certbot certificates |
Renew certificate; verify DNS |
# Enable debug mode temporarily
docker compose exec mixpost sed -i 's/APP_DEBUG=false/APP_DEBUG=true/' /var/www/html/.env
docker compose restart mixpost
# View detailed logs
docker compose logs -f mixpost
# Disable debug mode after troubleshooting
docker compose exec mixpost sed -i 's/APP_DEBUG=true/APP_DEBUG=false/' /var/www/html/.env
docker compose restart mixpost
# Access container shell
docker compose exec mixpost bash
# Access MySQL
docker compose exec mysql mysql -u root -p
# Access Redis CLI
docker compose exec redis redis-cli -a password
# View environment variables
docker compose exec mixpost env
# Check disk usage inside container
docker compose exec mixpost df -h
Any questions?
Feel free to contact us. Find all contact information on our contact page.