A GitHub Action to automatically deploy Mautic (open-source marketing automation) to DigitalOcean with zero configuration.
- π One-click deployment - Deploy Mautic in minutes, not hours
- π₯οΈ Automatic VPS creation - Creates and configures DigitalOcean droplets
- π Smart resizing - Automatically resizes droplets when you change vps-size in config
- π SSL/HTTPS support - Automatic Let's Encrypt SSL certificates (when domain provided)
- π³ Docker-based - Reliable, containerized deployment with Apache
- π§ Email ready - Pre-configured for email marketing campaigns
- π¨ Custom themes/plugins - Support for custom Mautic extensions via Composer or direct GitHub/ZIP installation
- ποΈ Custom Docker Images - Automatically builds custom images with your plugins pre-installed (build-time approach)
- βοΈ Cron jobs - Automated background tasks for optimal performance
- π Basic monitoring - DigitalOcean monitoring, container logging, and deployment artifacts
Minimum Recommended VPS Size: s-1vcpu-2gb (2GB RAM, $12/month)
- MySQL 8.0 baseline: ~400-500MB RAM
- Mautic application: ~300-400MB RAM
- OS and services: ~200-300MB RAM
- Growth buffer for database and cache: ~500MB
Using a 1GB droplet will cause crashes after a few days as:
- Database grows with contacts/campaigns
- Cron jobs increase memory pressure
- MySQL gets OOM-killed, corrupting the database
- System becomes unstable even with swap space
π‘ Recommended sizes by usage:
- Small/Testing:
s-1vcpu-2gb(up to 5K contacts) - Medium:
s-2vcpu-4gb(up to 50K contacts) - Large:
s-4vcpu-8gb(50K+ contacts)
- DigitalOcean account with API token (see required permissions below)
- SSH key pair for server access (uploaded to your DigitalOcean account)
- Domain name (optional, can use IP address)
Your DigitalOcean API token must have the following permissions:
Required Scopes:
droplet:create- Create new VPS instancesdroplet:read- Read droplet informationdroplet:delete- Delete droplets (for cleanup)ssh_key:read- Access SSH keys for droplet creationdomain:read- Read domain information (if using custom domain)domain:write- Manage DNS records (if using custom domain)
How to create a token:
- Go to DigitalOcean Control Panel β API β Personal Access Tokens
- Click "Generate New Token"
- Set name (e.g., "GitHub Mautic Deploy")
- Select "Full Access" or manually select the scopes listed above
- Copy the token immediately (you won't see it again)
Add these secrets to your GitHub repository (Settings β Secrets and variables β Actions):
DIGITALOCEAN_TOKEN=your_do_api_token
SSH_PRIVATE_KEY=your_ssh_private_key
MAUTIC_PASSWORD=your_admin_password
MYSQL_PASSWORD=your_mysql_password
MYSQL_ROOT_PASSWORD=your_mysql_root_password
π Recommended: Generate a Dedicated SSH Key for Automation
For security, create a new SSH key specifically for this GitHub Action:
# Generate a new SSH key (without passphrase for automation)
ssh-keygen -t ed25519 -f ~/.ssh/mautic_deploy_key -N "" -C "mautic-github-action"
# Add the public key to your DigitalOcean account
cat ~/.ssh/mautic_deploy_key.pub
# Copy this output and add it in DigitalOcean Control Panel β Settings β Security β SSH KeysSSH_PRIVATE_KEY: The content of your dedicated private SSH key
- Use the new key:
cat ~/.ssh/mautic_deploy_key - Copy the entire file content including
-----BEGIN OPENSSH PRIVATE KEY-----and-----END OPENSSH PRIVATE KEY----- β οΈ Key must have NO passphrase for automation
Security Benefits:
- π Separate key limits blast radius if compromised
- π« Can be easily revoked without affecting other services
- π Clear audit trail for automation access
- π Can be rotated independently
Make sure your SSH public key is already added to your DigitalOcean account before running the action.
Create .github/workflows/deploy-mautic.yml:
name: Deploy Mautic
on:
workflow_dispatch:
inputs:
vps_name:
description: 'VPS Name'
required: true
default: 'mautic-server'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy Mautic
uses: escopecz/mautic-deploy-action@v1
with:
vps-name: ${{ inputs.vps_name }}
vps-size: 's-2vcpu-2gb'
vps-region: 'nyc1'
domain: 'mautic.yourdomain.com'
email: '[email protected]'
mautic-password: ${{ secrets.MAUTIC_PASSWORD }}
mysql-password: ${{ secrets.MYSQL_PASSWORD }}
mysql-root-password: ${{ secrets.MYSQL_ROOT_PASSWORD }}
do-token: ${{ secrets.DIGITALOCEAN_TOKEN }}
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}- Go to your repository's
Actionstab - Select "Deploy Mautic" workflow
- Click "Run workflow"
- Enter your VPS name and click "Run workflow"
| Parameter | Description | Example |
|---|---|---|
vps-name |
Name for the DigitalOcean droplet | mautic-production |
email |
Admin email address | [email protected] |
mautic-password |
Admin password (use secrets!) | ${{ secrets.MAUTIC_PASSWORD }} |
mysql-password |
MySQL user password | ${{ secrets.MYSQL_PASSWORD }} |
mysql-root-password |
MySQL root password | ${{ secrets.MYSQL_ROOT_PASSWORD }} |
do-token |
DigitalOcean API token | ${{ secrets.DIGITALOCEAN_TOKEN }} |
ssh-private-key |
SSH private key for server access | ${{ secrets.SSH_PRIVATE_KEY }} |
| Parameter | Description | Default | Example |
|---|---|---|---|
vps-size |
DigitalOcean droplet size (automatically resizes if changed) | s-2vcpu-2gb |
s-4vcpu-8gb |
vps-region |
DigitalOcean region | nyc1 |
fra1, lon1, sgp1 |
domain |
Custom domain name | (uses IP) | mautic.example.com |
mautic-version |
Mautic Docker image version | 6.0.5-apache |
6.0.4-apache |
mautic-port |
Port for Mautic application | 8001 |
8080 |
themes |
Custom themes (Packagist packages or GitHub ZIP URLs with optional parameters, one per line) | "" |
|
plugins |
Custom plugins (Packagist packages or GitHub ZIP URLs with optional parameters, one per line) | "" |
|
mysql-database |
MySQL database name | mautic |
mautic_prod |
mysql-user |
MySQL username | mautic |
mautic_user |
| Output | Description |
|---|---|
vps-ip |
IP address of the created VPS |
mautic-url |
Full URL to access Mautic |
deployment-log |
Path to deployment log file |
This action supports flexible installation of custom plugins and themes from both public Packagist packages and private GitHub repositories.
themes: |
vendor/theme-name:^1.0
another-vendor/custom-theme:dev-main
https://github.com/user/theme-repo/archive/refs/heads/main.zip
plugins: |
vendor/plugin-name:^2.0
another-vendor/custom-plugin:^1.5
https://github.com/user/plugin-repo/archive/refs/heads/main.zipFor private repositories or custom directory names, use URL parameters:
themes: |
https://github.com/company/private-theme/archive/main.zip?directory=CompanyTheme&token=${{ secrets.COMPANY_GITHUB_TOKEN }}
https://github.com/vendor/premium-theme/archive/v2.1.0.zip?directory=PremiumTheme&token=${{ secrets.VENDOR_GITHUB_TOKEN }}
vendor/public-theme:^2.0
plugins: |
https://github.com/company/private-plugin/archive/main.zip?directory=CompanyPlugin&token=${{ secrets.COMPANY_GITHUB_TOKEN }}
https://github.com/vendor/custom-integration/archive/v1.5.2.zip?directory=CustomIntegration&token=${{ secrets.VENDOR_GITHUB_TOKEN }}
vendor/public-plugin:^1.0
https://github.com/public-repo/mautic-plugin/archive/refs/heads/main.zip?directory=PublicPlugin| Parameter | Description | Required | Example |
|---|---|---|---|
directory |
Custom directory name where the package will be extracted | No | CompanyPlugin, CustomTheme |
token |
GitHub Personal Access Token for private repositories | For private repos | ${{ secrets.COMPANY_GITHUB_TOKEN }} |
plugins: |
https://github.com/user/repo/archive/refs/heads/main.zipplugins: |
https://github.com/user/repo/archive/refs/heads/main.zip?directory=MyCustomNameplugins: |
https://github.com/company/private-repo/archive/main.zip?token=${{ secrets.COMPANY_GITHUB_TOKEN }}plugins: |
https://github.com/company/private-repo/archive/main.zip?directory=CompanyPlugin&token=${{ secrets.COMPANY_GITHUB_TOKEN }}For private repositories, you'll need to create GitHub Personal Access Tokens:
- Go to GitHub Settings > Personal Access Tokens
- Click "Generate new token" β "Fine-grained personal access token"
- Select the specific repositories you want to access
- Grant "Contents" read permission
- Copy the token and add it to your GitHub repository secrets
Important: Secret names cannot start with GITHUB_. Use names like:
COMPANY_GITHUB_TOKEN(for company/* repos)VENDOR_GITHUB_TOKEN(for vendor/* repos)CHIMPINO_GITHUB_TOKEN(for chimpino/* repos)
In your GitHub repository settings, create these secrets:
COMPANY_GITHUB_TOKEN = ghp_xxxxxxxxxxxxxxxxxxxx # Access to company/* repos
VENDOR_GITHUB_TOKEN = ghp_yyyyyyyyyyyyyyyyyyyy # Access to vendor/* repos
CHIMPINO_GITHUB_TOKEN = ghp_zzzzzzzzzzzzzzzzzz # Access to chimpino/* repos
- uses: escopecz/mautic-deploy-action@v1
with:
vps-name: 'mautic-with-plugins'
email: '[email protected]'
plugins: |
https://github.com/chimpino/stripe-plugin/archive/refs/heads/6.x.zip?directory=StripeBundle&token=${{ secrets.CHIMPINO_GITHUB_TOKEN }}
https://github.com/company/analytics-plugin/archive/v1.0.zip?directory=AnalyticsPlugin&token=${{ secrets.COMPANY_GITHUB_TOKEN }}
vendor/public-plugin:^2.0
themes: |
https://github.com/company/premium-theme/archive/main.zip?directory=PremiumTheme&token=${{ secrets.COMPANY_GITHUB_TOKEN }}
vendor/free-theme:^1.5
# ... other required parameters- Simplicity: No YAML parsing - just standard URL parameters
- Security: Different tokens for different repositories with minimal permissions
- Organization: Clear directory naming for better plugin/theme management
- Flexibility: Mix public packages, private repositories, and Packagist packages
- Maintenance: Easy to update individual packages without affecting others
- Intuitive: Standard URL parameter format familiar to developers
- Debugging: Clear error messages show which specific package failed and why
- uses: escopecz/mautic-deploy-action@v1
with:
vps-name: 'my-mautic'
email: '[email protected]'
# ... other required parameters- uses: escopecz/mautic-deploy-action@v1
with:
vps-name: 'mautic-production'
vps-size: 's-4vcpu-8gb'
domain: 'marketing.example.com'
email: '[email protected]'
# ... other parameters- uses: escopecz/mautic-deploy-action@v1
with:
vps-name: 'mautic-with-plugins'
email: '[email protected]'
plugins: |
https://github.com/chimpino/stripe-plugin/archive/refs/heads/6.x.zip?directory=StripeBundle&token=${{ secrets.CHIMPINO_GITHUB_TOKEN }}
vendor/public-plugin:^2.0
themes: |
https://github.com/company/custom-theme/archive/main.zip?directory=CustomTheme&token=${{ secrets.COMPANY_GITHUB_TOKEN }}
# ... other parameters- uses: escopecz/mautic-deploy-action@v1
with:
vps-name: 'mautic-${{ github.event.inputs.environment }}'
domain: '${{ github.event.inputs.environment }}.mautic.example.com'
# ... other parametersYou can install custom themes and plugins from Packagist using Composer packages:
themes: |
vendor/custom-theme:^1.0
another-vendor/modern-theme:dev-main
plugins: |
vendor/analytics-plugin:^2.0
vendor/social-plugin:^1.5The action will use composer require to install these packages into your Mautic instance.
π― Recommended Approach - For custom plugins hosted on GitHub or as ZIP files, the action can build a custom Docker image with your plugins pre-installed:
- uses: escopecz/mautic-deploy-action@v1
with:
vps-name: 'mautic-custom'
email: '[email protected]'
# Custom plugins (comma-separated URLs)
plugins: |
https://github.com/youruser/StripeBundle/archive/refs/heads/6.x.zip,
https://github.com/company/CustomCRM/archive/refs/tags/v2.1.0.zip
# Custom themes (comma-separated URLs)
themes: |
https://github.com/youruser/CustomTheme/archive/refs/heads/main.zip
# ... other required parametersSupported URL Formats:
- GitHub Archives:
https://github.com/user/repo/archive/refs/heads/main.zip - Tagged Releases:
https://github.com/user/repo/archive/refs/tags/v1.0.0.zip - Direct ZIP Files:
https://example.com/plugin.zip - Private Repositories:
https://[email protected]/user/private-repo/archive/refs/heads/main.zip
For private repositories, you can use GitHub Personal Access Tokens in the URL:
plugins: |
https://[email protected]/yourcompany/private-plugin/archive/refs/heads/main.zip,
https://[email protected]/yourcompany/premium-plugin/archive/refs/tags/v1.0.0.zip
themes: |
https://[email protected]/yourcompany/private-theme/archive/refs/heads/main.zipSetting up GitHub Personal Access Token:
- Go to GitHub β Settings β Developer settings β Personal access tokens β Tokens (classic)
- Click "Generate new token (classic)"
- Select scopes:
repo(for private repositories) - Copy the token (starts with
ghp_) - Use format:
https://[email protected]/owner/repo/archive/refs/heads/branch.zip
Security Notes:
- Store tokens in GitHub Secrets, not directly in workflow files
- Use fine-grained tokens with minimal repository access
- Consider using GitHub App installations for organization repositories
Advantages:
- β Faster Startup: Plugins pre-installed during image build
- β More Reliable: No network dependencies at runtime
- β Official Pattern: Follows Mautic Docker team's recommended approach
- β Zero Complexity: No SSH keys or runtime installation needed
π Full Documentation: Custom Plugins Deployment Guide
Customize database settings:
mysql-database: 'mautic_production'
mysql-user: 'mautic_admin'
mysql-password: ${{ secrets.MYSQL_PASSWORD }}
mysql-root-password: ${{ secrets.MYSQL_ROOT_PASSWORD }}| Size | vCPUs | RAM | Use Case |
|---|---|---|---|
s-1vcpu-1gb |
1 | 1GB | Testing/Development |
s-2vcpu-2gb |
2 | 2GB | Small campaigns (<10k contacts) |
s-2vcpu-4gb |
2 | 4GB | Medium campaigns (10k-50k contacts) |
s-4vcpu-8gb |
4 | 8GB | Large campaigns (50k+ contacts) |
1. DigitalOcean API Permission Error
Error: You are missing the required permission ssh_key:read
- Your DigitalOcean API token doesn't have sufficient permissions
- Generate a new token with "Full Access" or ensure it includes:
droplet:create,droplet:read,droplet:delete,ssh_key:read,domain:read,domain:write - Update your
DIGITALOCEAN_TOKENsecret with the new token
2. SSH Connection Failed
Error: Permission denied (publickey)
- Most Common Cause: SSH private key doesn't match any key in your DigitalOcean account
- Ensure your SSH public key is added to DigitalOcean: Settings β Security β SSH Keys
- The action automatically generates the fingerprint and finds the matching key
- Verify your SSH private key is correctly formatted in secrets (include the full key with headers)
- Make sure your SSH public key is added to your DigitalOcean account before running the action
- Use an SSH key without a passphrase for automation
- Check the action logs for debugging information about key verification
3. Mautic Site Offline - "Currently offline due to error"
The site is currently offline due to encountering an error.
Common Causes:
- Out of Memory (OOM): Most common on 1GB droplets after a few days
- MySQL 8.0 requires ~400-500MB baseline memory
- Database growth + cron jobs can exceed available RAM
- Solution: Update
vps-sizetos-1vcpu-2gbor larger in your workflow YAML and re-run the action - The action will automatically resize your droplet with minimal downtime
- Database Corruption: From repeated OOM kills or power issues
- Check logs:
docker logs mautic_db --tail 50 - May need to restore from backup or recreate
- Check logs:
- Cache Issues: Corrupted cache files
- SSH into server:
docker exec mautic_web rm -rf /var/www/html/var/cache/*
- SSH into server:
4. Domain Not Pointing to Server
Error: Domain example.com does not point to VPS IP
- Update your DNS A record to point to the VPS IP
- Wait for DNS propagation (can take up to 24 hours)
4. SSL Certificate Failed
Error: SSL certificate installation failed
- Ensure domain is pointing to the server before deployment
- Check that port 80 and 443 are not blocked
If you're getting Permission denied (publickey) errors, follow these steps:
1. Verify Your SSH Key
# Generate fingerprint from your dedicated private key
ssh-keygen -l -f ~/.ssh/mautic_deploy_key
# View your public key (to add to DigitalOcean if missing)
cat ~/.ssh/mautic_deploy_key.pub2. Check DigitalOcean SSH Keys
# List all SSH keys in your DO account
doctl compute ssh-key list
# Find the fingerprint that matches your key3. Verify Key Format in GitHub Secrets
SSH_PRIVATE_KEY: Must include headers and be the full private key:-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn ... (rest of key content) ... -----END OPENSSH PRIVATE KEY------ The action automatically finds the matching key in your DigitalOcean account
4. Test SSH Connection Manually
ssh -i ~/.ssh/mautic_deploy_key root@YOUR_VPS_IP- Check the deployment log artifact uploaded after each run
- SSH into your server:
ssh root@YOUR_VPS_IP - View Docker logs:
docker-compose logs -f - Check Mautic logs:
tail -f /var/www/logs/*.log
- Uses DigitalOcean's private networking
- Automatic SSL/TLS encryption with Let's Encrypt
- Database passwords are securely managed
- Regular security updates via official Docker images
- SSH key-based authentication only
The deployment includes basic monitoring:
- DigitalOcean VPS monitoring (CPU, RAM, disk usage)
- Docker container status monitoring
- Application logs captured in
/var/www/logs - Deployment log artifacts uploaded to GitHub Actions
- Automated Mautic cron jobs for maintenance tasks
Note: For production environments, consider adding:
- Docker health checks in docker-compose.yml
- Log rotation with logrotate
- External monitoring (Uptime Robot, Pingdom, etc.)
- Application performance monitoring (APM)
- Database performance monitoring
To update Mautic to a new version:
- Change the
mautic-versionparameter - Re-run the workflow
- The action will pull the new image and restart services
Important directories to backup:
/var/www/mautic_data- Mautic files and uploads/var/www/mysql_data- Database files
For high-traffic deployments:
- Use larger VPS sizes (
s-4vcpu-8gbor higher) - Consider dedicated database servers
- Implement Redis for session storage
- Use CDN for static assets
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
- Mautic - Open-source marketing automation
- DigitalOcean - Cloud infrastructure
- Docker - Containerization platform