Self-Host Mautic with Docker Compose: Step-by-Step Guide

by Fahim

Paying hundreds of dollars a month to HubSpot or ActiveCampaign just to send basic automated email sequences is a massive waste of money. Let’s fix that. We are going to spin up Mautic 5 on your own VPS using Docker Compose, complete with a MariaDB database and automated cron jobs that actually run instead of silently failing.

Docker Compose terminal logs for a self-hosted Mautic installation
Docker Compose terminal logs for a self-hosted Mautic installation

Why Docker Compose is the Only Way to Run Mautic Without Going Insane

If you have ever tried to install Mautic directly on a bare-metal Ubuntu server, you already know it is an absolute nightmare. Mautic is notoriously picky about PHP versions, extension configurations, and folder permissions. One unattended system update can upgrade your PHP version and instantly break your entire marketing automation stack.

Docker saves us from this dependency hell. By packaging PHP, Apache, and all the required libraries into a single container, we guarantee that Mautic runs in an isolated, predictable environment. If you have already set up other tools using my guides—like when I showed you how to self-host Umami analytics with Docker Compose—you already know how much cleaner a containerized VPS setup is.

With Docker Compose, we define the whole stack—the Mautic app, the MariaDB database, and helper services—in a single file. This makes backups, migrations, and updates incredibly simple.

The Docker Compose Configuration for Mautic 5

We will use the official Mautic image built on Apache. It is by far the easiest version to configure because it handles the web server setup right out of the box. Create a new directory on your server and save the following configuration as docker-compose.yml.

Here is the complete configuration file for your stack:

version: '3.8' services: mautic-db: image: mariadb:10.11 container_name: mautic_db restart: always environment: MYSQL_ROOT_PASSWORD: super_secure_root_password_here MYSQL_DATABASE: mautic MYSQL_USER: mauticuser MYSQL_PASSWORD: super_secure_db_password_here volumes: - mautic_db_data:/var/lib/mysql command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci mautic-app: image: mautic/mautic:v5-apache container_name: mautic_app restart: always depends_on: - mautic-db ports: - "8080:80" environment: MAUTIC_DB_HOST: mautic-db MAUTIC_DB_USER: mauticuser MAUTIC_DB_PASSWORD: super_secure_db_password_here MAUTIC_DB_NAME: mautic MAUTIC_RUN_CRON_JOBS: "false" volumes: - mautic_app_data:/var/www/html volumes: mautic_db_data: mautic_app_data:

I highly recommend sticking to the LTS release of MariaDB. You can double-check the latest tags directly on the MariaDB Docker Hub page. Note that I passed a specific character set command to MariaDB. Mautic absolutely requires utf8mb4 to handle modern character sets, emojis, and tracking data without throwing database errors mid-campaign.

Configuring Environment Variables and Port Mapping

In the configuration above, I mapped port 8080 on the host to port 80 inside the container. This keeps things from clashing with any web servers already running on your host. You will eventually want to route this traffic through a reverse proxy like Nginx, Caddy, or a Cloudflare Tunnel to handle SSL certificates.

Do not hardcode your database passwords in the main configuration. It is bad practice and a security risk. Instead, we will use a .env file to keep things clean and secure.

Create a .env file in the same directory:

DB_ROOT_PASSWORD=your_ultra_secure_root_pass
DB_PASSWORD=your_mautic_db_pass
MAUTIC_URL=https://mautic.yourdomain.com

Then, update your docker-compose.yml to reference these variables using the ${VARIABLE_NAME} syntax. This keeps your secrets out of your main configuration files and makes it much easier to track your setups in private git repositories without leaking credentials.

Spinning Up the Containers and Running the Installer

With your configuration files ready, let’s fire up the containers. Run this command in your terminal:

docker compose up -d

The first run will pull down the MariaDB and Mautic images, set up the internal network, mount the volumes, and launch the services. You can monitor the startup process by tailing the logs:

docker compose logs -f mautic-app

Once the logs show that Apache is up and running, open your browser and head to http://your_server_ip:8080. You should see the Mautic installation wizard.

Since we passed the database credentials as environment variables, Mautic should automatically detect the database host, database name, and user. If it asks for them manually, enter mautic-db as the database host (matching the service name in our YAML file) along with the credentials you defined in your .env file.

The Big Gotcha: Setting Up Cron Jobs That Actually Work

This is where almost every self-hosted Mautic setup falls flat. Out of the box, Mautic does not process emails, update campaign segments, or trigger automation steps on its own. It relies on system cron jobs to trigger these actions behind the scenes.

While you can configure cron jobs inside the container, doing so bloats your image and makes updates a headache. The most reliable method I have found is to trigger the container’s internal PHP commands directly from your host server’s crontab.

Open your host server’s crontab editor:

crontab -e

Add the following three cron jobs to run the essential Mautic console commands. Make sure to adjust the paths and container names to match your actual setup:

# Update segments every 15 minutes
*/15 * * * * docker exec -u www-data mautic_app php /var/www/html/bin/console mautic:segments:update > /dev/null 2>&1 # Update campaigns every 15 minutes (offset by 5 minutes)
5,20,35,50 * * * * docker exec -u www-data mautic_app php /var/www/html/bin/console mautic:campaigns:update > /dev/null 2>&1 # Trigger campaign events every 15 minutes (offset by 10 minutes)
10,25,40,55 * * * * docker exec -u www-data mautic_app php /var/www/html/bin/console mautic:campaigns:trigger > /dev/null 2>&1

Running these commands as the www-data user inside the container using the -u www-data flag is absolutely critical. If you run them as root, you will break the file permissions inside your mounted volumes, causing Mautic to crash the next time it tries to write to its cache directories. For a complete list of available console commands, check out the official Mautic documentation.

Connecting Webhooks and External Integrations

Once your cron jobs are running, Mautic can process inbound and outbound events. If you want to feed leads into Mautic from external forms or send contact updates to other platforms, you will need to configure webhooks.

To test these integrations locally before deploying them to your live production server, you can follow my guide on how to test webhooks locally with Cloudflare Tunnels. This lets you safely route external webhooks directly to your local Docker environment.

If you are collecting leads through spreadsheets, you can also build custom scripts to push those contacts into your new Mautic instance. I wrote a detailed tutorial showing how to send webhooks from Google Sheets with Apps Script, which you can easily adapt to sync data with Mautic’s API endpoints.

Troubleshooting Common Mautic Docker Errors

Even with Docker, you will likely run into a few annoying configuration issues during your initial setup. Here is how I fix them when they pop up.

Database Connection Refused

If you see a “Database connection refused” error during the web installation, it is usually because the MariaDB container takes longer to boot up than the Mautic container. When Mautic tries to connect on its first run, the database is not ready to accept connections yet.

To fix this, just restart the Mautic application container after waiting a few seconds for the database to finish initializing:

docker compose restart mautic-app

Permission Denied in Cache Directories

If Mautic displays a blank white screen or throws permission errors in the logs, the file permissions on your host volumes are likely messed up. You can reset the ownership of the files inside the container back to the Apache user by running:

docker exec -u 0 mautic_app chown -R www-data:www-data /var/www/html/var/cache

This command logs in as root inside the container (using the -u 0 flag) and updates the folder ownership so the web server can read and write cache files properly again.

Frequently Asked Questions

Can I run Mautic on a cheap $5 VPS?

You can run it on a 1GB RAM VPS for basic testing, but for production use with active campaigns, you really need at least 2GB of RAM. Mautic uses PHP and MariaDB, both of which will eat up memory when processing large email segments or importing contacts.

How do I update Mautic to a newer version using Docker?

To update Mautic, back up your database and your mounted volumes first. Then, pull the latest image tag and recreate your containers:

docker compose pull
docker compose up -d --remove-orphans

Which email service provider should I use with Mautic?

Mautic does not send emails directly; it connects to external SMTP or API delivery services. Amazon SES is by far the cheapest option for high-volume sending, while services like Postmark or Mailgun offer excellent deliverability and easier setup if you have a smaller list.

Next Steps for Your Self-Hosted Stack

Now that you have a self-hosted marketing automation platform up and running, you will want to build automated workflows around it. Instead of paying for expensive middleware tools, you can connect Mautic to other self-hosted services.

To build automated workflows that sync contacts and trigger actions across your entire stack, check out my guide to self-host n8n with Docker Compose and PostgreSQL.

Official resources

all_in_one_marketing_tool