Why would you do this? Maybe you are tired of shared hosting and its arbitrary charges, or maybe you already have a Node.js app running on a VPS and want to add a WordPress site, blog, or landing page alongside it — and want everything on one server.

Either way, this is how I do it.

What is Coolify and why do you need it?

Coolify is a self-hosted admin panel that lets you run Dockerized apps on your own server. It's similar to Heroku or Vercel but free, lives on your VPS, offers GitHub-based automatic deployment, and acts as a reverse proxy — so you can run many projects on a single VPS without any manual Nginx configuration.

Coolify is so well made that you don't even need to know Docker to use it. Your projects don't need Dockerfiles. That said, intermediate Docker knowledge helps if you want to go beyond the basics.

Install Coolify on your VPS and it handles everything else — Docker, Git, SSL, reverse proxy. You just deploy.

Install WordPress with Coolify

Coolify ships with three WordPress service options:

  1. WordPress with MariaDB
  2. WordPress with MySQL
  3. WordPress without database

Go with option one — WordPress with MariaDB. Click it, click Deploy, and Coolify spins up WordPress and MariaDB in Docker containers and gives you a URL to complete the WordPress installation.

Before moving on, modify the WordPress service volume in the Docker Compose file so the WordPress folder is accessible on the server:

volumes:
  - './wordpress:/var/www/html'

This mounts the WordPress files to your server's filesystem, which you'll need for the steps below.

Add PhpMyAdmin

To view and manage the database, add PhpMyAdmin as an additional service in the Docker Compose file:

phpmyadmin:
  depends_on:
    - mariadb
  image: phpmyadmin/phpmyadmin
  ports:
    - '8070:80'
  environment:
    - PMA_HOST=mariadb
    - PMA_PASSWORD=$SERVICE_PASSWORD_ROOT
    - UPLOAD_LIMIT=300000000

Make sure depends_on and PMA_HOST both reference the database service name — mariadb in this case.

After deploying, PhpMyAdmin is accessible at your VPS IP or Coolify URL on port :8070. The login is root with the MariaDB Root Password from the Coolify dashboard.

The UPLOAD_LIMIT value sets the maximum database import size. 300MB covers most migration scenarios.

One of the advantages of running on Docker is that you can stop the PhpMyAdmin container when you're not using it — there's no reason to have it exposed on the internet permanently. Stop it via SSH:

docker ps                          # get the container ID
docker stop <CONTAINER_ID>

To start it again, restart the service stack from the Coolify dashboard — it comes back up in seconds.

Fix: WordPress asking for FTP credentials

When running WordPress on Docker it will ask for FTP credentials when installing or updating plugins. Fix this by adding FS_METHOD to the WordPress config via the Docker Compose environment:

services:
  wordpress:
    image: 'wordpress:latest'
    environment:
      - "WORDPRESS_CONFIG_EXTRA=
        define('FS_METHOD', 'direct');"

The quotes around WORDPRESS_CONFIG_EXTRA allow multiple define statements on separate lines, which you'll need in the next step.

Enable WordPress Multisite (optional)

Skip this if you don't need multisite.

If you've mounted the WordPress volume, the files are at /data/coolify/services/<project-id>/wordpress on your server. Edit wp-config.php directly and add:

define( 'WP_ALLOW_MULTISITE', true );

This unlocks the Network Setup option under Dashboard → Tools. After completing the setup wizard, inject the generated config constants back into the Docker Compose file via WORDPRESS_CONFIG_EXTRA:

- "WORDPRESS_CONFIG_EXTRA=
  define('FS_METHOD', 'direct');
  define('WP_ALLOW_MULTISITE', true);
  define('MULTISITE', true);
  define('SUBDOMAIN_INSTALL', false);
  define('DOMAIN_CURRENT_SITE', 'your-domain.com');
  define('PATH_CURRENT_SITE', '/');
  define('SITE_ID_CURRENT_SITE', 1);
  define('BLOG_ID_CURRENT_SITE', 1);"

Multisite also requires .htaccess rules. Since WordPress runs in a Docker container, mount .htaccess as a volume:

volumes:
  - './.htaccess:/var/www/html/.htaccess'

After restarting the stack, the .htaccess file appears in Coolify's Storage tab. Click Convert to file and paste the network .htaccess rules:

RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

Save and restart the stack. Multisite should work correctly across all network sites.

Add FTP access

A quick note before adding FTP: it works, but it's slow. For large file transfers — migrating an uploads folder that's several GB — FTP will take hours. Use rsync instead for anything substantial. It's faster, simpler, and only requires SSH access.

That said, FTP is useful for quick file access via a GUI client. Add it to the Docker Compose file:

ftp:
  image: delfer/alpine-ftp-server
  network_mode: host
  ports:
    - '21:21'
    - '21000-21010:21000-21010'
  environment:
    - USERS=ftpadmin|yourpassword|/var/www/html|10001
  volumes:
    - './wordpress:/var/www/html'

This creates an FTP user with access to the WordPress folder. The format for USERS is username|password|folder|uid.

By default the FTP user has read-only access. To allow write permissions on wp-content, SSH into the VPS and run:

sudo chown -R 10001:10001 wp-content

Connect via any FTP client on port 21. When you're done, stop the FTP container the same way as PhpMyAdmin — no reason to keep port 21 open permanently.

Increase the WordPress upload limit

The default PHP upload limits are too low for most real-world use. Since WordPress runs in Docker, you can't edit php.ini directly — but you can mount a custom config file.

Add a volume to the WordPress service:

volumes:
  - './wordpress:/var/www/html'
  - './uploads.ini:/usr/local/etc/php/conf.d/uploads.ini'

After restarting the stack, the uploads.ini file appears in Coolify's Storage tab. Click Convert to file and add:

file_uploads = On
memory_limit = 500M
upload_max_filesize = 500M
post_max_size = 500M
max_execution_time = 600

Save and restart. These limits cover even the most demanding scenarios.

The full Docker Compose file

Here's the complete Docker Compose file with all services included:

services:
  wordpress:
    image: 'wordpress:latest'
    volumes:
      - './wordpress:/var/www/html'
      - './.htaccess:/var/www/html/.htaccess'
      - './uploads.ini:/usr/local/etc/php/conf.d/uploads.ini'
    environment:
      - SERVICE_FQDN_WORDPRESS
      - WORDPRESS_DB_HOST=mariadb
      - WORDPRESS_DB_USER=$SERVICE_USER_WORDPRESS
      - WORDPRESS_DB_PASSWORD=$SERVICE_PASSWORD_WORDPRESS
      - WORDPRESS_DB_NAME=wordpress
      - "WORDPRESS_CONFIG_EXTRA=
        define('FS_METHOD', 'direct');
        define('WP_ALLOW_MULTISITE', true);
        define('MULTISITE', true);
        define('SUBDOMAIN_INSTALL', false);
        define('DOMAIN_CURRENT_SITE', 'your-domain.com');
        define('PATH_CURRENT_SITE', '/');
        define('SITE_ID_CURRENT_SITE', 1);
        define('BLOG_ID_CURRENT_SITE', 1);"
    depends_on:
      - mariadb
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://127.0.0.1']
      interval: 2s
      timeout: 10s
      retries: 10

  mariadb:
    image: 'mariadb:11'
    volumes:
      - 'mariadb-data:/var/lib/mysql'
    environment:
      - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_ROOT
      - MYSQL_DATABASE=wordpress
      - MYSQL_USER=$SERVICE_USER_WORDPRESS
      - MYSQL_PASSWORD=$SERVICE_PASSWORD_WORDPRESS
    healthcheck:
      test: ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized']
      interval: 5s
      timeout: 20s
      retries: 10

  phpmyadmin:
    depends_on:
      - mariadb
    image: phpmyadmin/phpmyadmin
    ports:
      - '8070:80'
    environment:
      - PMA_HOST=mariadb
      - PMA_PASSWORD=$SERVICE_PASSWORD_ROOT
      - UPLOAD_LIMIT=300000000

  ftp:
    image: delfer/alpine-ftp-server
    network_mode: host
    ports:
      - '21:21'
      - '21000-21010:21000-21010'
    environment:
      - USERS=ftpadmin|yourpassword|/var/www/html|10001
    volumes:
      - './wordpress:/var/www/html'

Remember to stop the PhpMyAdmin and FTP containers when you're not using them.

Wrapping up

That's it — WordPress running in Docker on your own VPS, with MariaDB, PhpMyAdmin, FTP, and proper upload limits. No shared hosting, no arbitrary plan restrictions, all on a $4-6/month Hetzner VPS that will outperform most shared hosting plans.

Coolify makes what used to be a significant amount of server configuration into a straightforward deployment process. Once it's set up, adding more WordPress sites — or any other Dockerized app — to the same VPS takes minutes.