Tag: wordpress

  • How to Install WordPress on Portainer with Caddy Proxy

    Running WordPress behind a modern, automatic HTTPS reverse proxy is easier than ever with Caddy and Portainer. In this guide, you’ll deploy WordPress and MariaDB as Docker containers, fronted by Caddy for TLS and smart HTTP handling, and you’ll manage it all through Portainer’s friendly UI. Follow along step by step to go from a clean host to a secure, production-ready WordPress site reachable at your domain.

    Understanding the Setup: Portainer, Caddy, WordPress

    At a high level, you’ll host three services: Caddy as the public-facing reverse proxy and TLS terminator, WordPress as the application, and MariaDB as the database. Caddy listens on ports 80 and 443, obtains and renews Let’s Encrypt certificates automatically, and forwards traffic to WordPress. WordPress only needs to be reachable by Caddy, keeping the app and database isolated from the public internet.

    Portainer sits alongside these services and gives you a web UI to deploy, monitor, and manage your Docker stack. Instead of running all commands by hand, you’ll paste a Docker Compose file into Portainer and click Deploy. Portainer will pull images, create networks and volumes (or use the ones you prepared), and launch the stack.

    Networking-wise, you’ll use a public “web” network for anything the proxy needs to reach and a private “wp” network for WordPress-to-DB traffic. Persistent Docker volumes will store your database and WordPress files so container restarts or image updates don’t erase your data.

    Prerequisites: Domain, DNS, and Server Access

    You need a domain name you control so that Caddy can provision HTTPS. Plan which hostname you’ll use, for example example.com and optionally www.example.com. You’ll point these DNS records to your server’s public IP address, and once the DNS is live, Caddy will request a valid certificate from Let’s Encrypt on first run.

    A Linux server (cloud VM or dedicated) with a public IPv4 (and optionally IPv6) is required. You’ll need shell access with sudo privileges to install Docker and prepare directories. Make sure ports 80 and 443 are open in your firewall and not in use by another service like Apache or Nginx.

    Have a text editor available on the server (nano or vim) for creating the Caddyfile, and keep strong, unique passwords ready for your database and WordPress admin. If you’re behind a CDN or DDoS protection service, ensure it’s configured to proxy 80/443 and pass traffic to your server.

    Install Docker and Portainer on Your Host

    You’ll use Docker to run isolated containers for Caddy, WordPress, and MariaDB. Portainer adds a convenient web UI for deploying stacks and reviewing logs. If you already have Docker installed, you can skip directly to launching Portainer; otherwise, install Docker first and add your user to the docker group so you can run it without sudo.

    # Install Docker the quick way
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    sudo usermod -aG docker $USER
    
    # Log out and back in (or run: newgrp docker) to apply group change
    
    # Run Portainer CE
    docker volume create portainer_data
    docker run -d 
      -p 8000:8000 -p 9443:9443 
      --name portainer 
      --restart=always 
      -v /var/run/docker.sock:/var/run/docker.sock 
      -v portainer_data:/data 
      portainer/portainer-ce:latest

    Once Portainer is up, access it at https://YOUR_SERVER_IP:9443 to complete the initial admin setup. Add your local Docker environment when prompted. Confirm your firewall allows 9443 (Portainer), and remember you’ll later expose 80/443 for Caddy.

    Set Up a Docker Network and Persistent Volumes

    To keep things clean and secure, you’ll create a shared “web” network for the proxy and any public apps, plus a private “wp” network used by WordPress and the database. You’ll also prepare Docker volumes so your Caddy certificates, database data, and WordPress files persist across updates.

    # Public-facing network shared by Caddy and WordPress
    docker network create web
    
    # Persistent data volumes
    docker volume create caddy_data
    docker volume create db_data
    docker volume create wp_data

    This approach lets Portainer’s stack reference existing networks/volumes and ensures that certificates, database records, and uploads are not lost when you redeploy. If you prefer, you can let the stack create volumes automatically, but pre-creating them makes backups and maintenance simpler.

    Create a Caddyfile with Automatic HTTPS Rules

    Caddy’s configuration is simple and powerful. You’ll define your domains, enable automatic HTTPS, add basic security headers, and tell Caddy to reverse proxy requests to the WordPress container. Replace example.com with your real domain and set a valid email for Let’s Encrypt notices.

    # /opt/caddy/Caddyfile
    
    {
        email you@example.com
    }
    
    example.com, www.example.com {
        encode zstd gzip
        header {
            Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
            X-Content-Type-Options "nosniff"
            Referrer-Policy "strict-origin-when-cross-origin"
        }
        reverse_proxy wordpress:80
    }

    Create the directory and file on your host and secure it appropriately. For example, run: sudo mkdir -p /opt/caddy && sudo nano /opt/caddy/Caddyfile, then paste the content above. This file will be mounted into the Caddy container read-only so you can update it later without rebuilding the image.

    Prepare a Docker Compose Stack for WordPress

    You’ll define all three services—caddy, wordpress, and db—in a single Compose file so Portainer can deploy them as a stack. The caddy service binds ports 80/443 and mounts your Caddyfile and certificate storage volume; WordPress connects to MariaDB on the private network; and both Caddy and WordPress share the public “web” network so traffic can flow.

    # docker-compose.yml
    
    version: "3.8"
    
    services:
      caddy:
        image: caddy:2
        container_name: caddy
        restart: unless-stopped
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - caddy_data:/data
          - /opt/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
        networks:
          - web
          - wp
    
      db:
        image: mariadb:11
        container_name: wp-db
        restart: unless-stopped
        environment:
          - MARIADB_DATABASE=wordpress
          - MARIADB_USER=wp
          - MARIADB_PASSWORD=CHANGE_ME_STRONG
          - MARIADB_ROOT_PASSWORD=CHANGE_ME_VERY_STRONG
        volumes:
          - db_data:/var/lib/mysql
        networks:
          - wp
    
      wordpress:
        image: wordpress:latest
        container_name: wordpress
        restart: unless-stopped
        depends_on:
          - db
        environment:
          - WORDPRESS_DB_HOST=db:3306
          - WORDPRESS_DB_USER=wp
          - WORDPRESS_DB_PASSWORD=CHANGE_ME_STRONG
          - WORDPRESS_DB_NAME=wordpress
        volumes:
          - wp_data:/var/www/html
        networks:
          - web
          - wp
    
    volumes:
      caddy_data:
        external: true
      db_data:
        external: true
      wp_data:
        external: true
    
    networks:
      web:
        external: true
      wp:
        driver: bridge

    Edit the passwords and domain before deploying. If you changed any volume or network names earlier, update them here to match. In production, treat secrets carefully—consider moving credentials to Docker secrets or environment variables managed by your platform.

    Deploy the Stack in Portainer and Verify Logs

    Open Portainer at https://YOUR_SERVER_IP:9443, log in, and select your local Docker environment. Go to Stacks, click Add stack, give it a name like wordpress-with-caddy, and paste your docker-compose.yml content into the Web editor. Click Deploy the stack and watch as Portainer brings the services online.

    After deployment, open Containers in Portainer and check Logs for caddy. On first run, Caddy should solve HTTP-01 challenges and obtain certificates from Let’s Encrypt for your domain. If DNS isn’t pointed yet or ports 80/443 are blocked, you’ll see ACME errors; fix those and restart the caddy container.

    Also check the wordpress and wp-db logs for healthy startup messages. WordPress will wait for the database to be reachable. If you see connection errors, confirm credentials and that both services share the wp network.

    Point Your Domain DNS to the Server IP Address

    In your domain registrar or DNS host, create A (and AAAA if you have IPv6) records for your hostname(s) pointing to your server’s public IP. For example, create an A record for @ (example.com) and another for www, both to 203.0.113.10. A low TTL (like 300 seconds) can speed up changes during initial setup.

    DNS changes can take minutes to propagate. While you wait, you can run dig example.com or nslookup example.com from your local machine to verify the new records are live. Only after the domain resolves to your server will Let’s Encrypt issue a certificate successfully.

    Once propagation is complete, restart the caddy container from Portainer or run docker restart caddy on the host to trigger certificate issuance if it didn’t already. Then browse to https://example.com and confirm you see the WordPress installer with a valid padlock.

    Run WordPress Setup and Configure Site URLs

    Visit your domain at https://example.com to complete the WordPress installation wizard. Choose your language, set a site title, and create an admin username and strong password. When finished, log in to the dashboard to start configuring themes, plugins, and content.

    Check Settings → General and confirm both WordPress Address (URL) and Site Address (URL) use https and your correct domain. If you ever need to force HTTPS in the admin area, you can add define(‘FORCE_SSL_ADMIN’, true); to wp-config.php, but with Caddy handling TLS and reverse proxy headers, WordPress should detect HTTPS automatically.

    Go to Settings → Permalinks and choose a modern structure like Post name, then save. Create a test post, upload an image, and view it on the front end to confirm everything works over HTTPS. If you see mixed-content warnings, update any hard-coded http links in your theme or settings.

    Hardening, Backups, and Common Troubleshooting

    Harden your deployment by using least-privilege DB credentials (a dedicated non-root user, as shown), keeping images updated, and limiting write access where possible. Consider disabling xmlrpc if you don’t need it, enforce strong passwords and 2FA, and review security headers in your Caddyfile. Make sure only ports 80/443 are exposed publicly; the database should remain internal on the wp network.

    Backups should cover both the database and WordPress files. Dump the database on a schedule and archive the wp_data volume. For example, you can run a simple dump from the host: docker exec wp-db sh -c ‘exec mysqldump -u wp -p”$MARIADB_PASSWORD” wordpress’ > wp-$(date +%F).sql, and then copy wp_data for uploads and installed plugins/themes.

    If Caddy can’t obtain certificates, verify DNS points to your server, ports 80/443 are open, and no other service is binding those ports. For 502/504 errors, check container logs and that the caddy, wordpress, and db services share the correct networks. If you use a CDN like Cloudflare, ensure your SSL mode is Full (strict preferred) and that proxying is enabled for your A/AAAA records. Finally, confirm your server’s time is correct—certificate issuance can fail if the clock is far off.

    You now have a clean, maintainable WordPress stack running under Portainer with Caddy providing automatic HTTPS and smart reverse proxying. This setup keeps components decoupled, makes updates easy, and sets a strong foundation for performance and security. As you grow, you can add caching, staging environments, and CI/CD while preserving the same simple, containerized workflow.

Secret Link