How to Deploy Docker Compose Applications with Caddy Reverse Proxy and Free SSL on Ubuntu 24.04

Deploying an application with Docker is a convenient way to manage software dependencies, isolate services, and maintain consistent application environments. However, running a Docker container is only the first step when preparing an application for production.

A production-ready deployment also needs a reliable reverse proxy, secure HTTPS connections, persistent storage, container networking, automatic service recovery, and proper firewall configuration.

Docker Compose and Caddy provide a practical solution for managing these requirements on an Ubuntu 24.04 server.

Docker Compose allows you to define and manage multiple containers through a single YAML configuration file, while Caddy acts as a reverse proxy that automatically provisions and renews free SSL/TLS certificates.

In this tutorial, you will learn how to deploy a Docker Compose application on Ubuntu 24.04, configure Caddy as a Docker reverse proxy, connect application containers through a private Docker network, and enable automatic HTTPS for your domain. We will use a sample Nginx application to demonstrate the deployment process. You can adapt the same configuration for Node.js, Python, PHP, and other containerized applications.

By the end of this guide, you will have a working application deployment with:

  • Docker Engine and Docker Compose.

  • A containerized web application.

  • Caddy reverse proxy configuration.

  • Automatic HTTPS with free SSL certificates.

  • Persistent Docker volumes.

  • Private container-to-container networking.

  • Firewall configuration and deployment troubleshooting.

Prerequisites

Before starting, make sure you have:

  • An Ubuntu 24.04 server with root or sudo access.

  • A registered domain name.

  • A public IP address assigned to your server.

  • Access to your domain's DNS management.

  • Basic knowledge of Linux commands and SSH.

  • TCP ports 80 and 443 available for web traffic.

A dedicated server is particularly useful for production applications that require predictable CPU performance, sufficient memory, storage capacity, and control over the hosting environment.

1. What Is Docker Compose and Why Use It for Production Deployment?

Docker Compose is a tool for defining and managing multi-container Docker applications using a YAML configuration file.

Instead of manually creating and configuring individual containers, you can define application services, networks, volumes, environment variables, and restart policies in one configuration.

For example, a web application may require:

  • An application container to process requests.

  • A database container to store application data.

  • A reverse proxy to manage incoming HTTP and HTTPS traffic.

  • Persistent volumes to retain important application files.

Docker Compose allows these services to operate together through a single deployment configuration.

Why use Docker Compose for production applications?

  • Simplified deployment: Manage multiple containers through one Compose file.

  • Consistent environments: Reuse the same service configuration across development, testing, and production.

  • Persistent storage: Maintain application data through Docker volumes.

  • Container networking: Allow services to communicate through private Docker networks.

  • Automatic recovery: Configure restart policies to recover from unexpected container exits.

  • Easier maintenance: Update, restart, and inspect application services using Docker Compose commands.

Docker Compose is not a complete replacement for production monitoring, backup systems, or high-availability infrastructure. However, it provides a practical foundation for deploying and maintaining containerized applications.

2. Install Docker Engine and Docker Compose on Ubuntu 24.04

The first step is to install Docker Engine and the Docker Compose plugin on your Ubuntu 24.04 server. Docker Engine runs and manages containers, while Docker Compose defines how multiple containers work together.

Step 1: Update Ubuntu packages

Connect to your server through SSH and update the package index:

sudo apt update
sudo apt upgrade -y

Install the required packages:

sudo apt install -y ca-certificates curl

Step 2: Add the official Docker repository

Create a directory for the Docker repository signing key:

sudo install -m 0755 -d /etc/apt/keyrings

Download the official Docker GPG key:

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

Set the appropriate permissions:

sudo chmod a+r /etc/apt/keyrings/docker.asc

Next, add the official Docker APT repository:

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

Update the package index again:

sudo apt update

Step 3: Install Docker Engine and Compose

Install Docker Engine, Docker CLI, containerd, Buildx, and the Docker Compose plugin:

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Enable Docker to start automatically when the server boots:

sudo systemctl enable --now docker

Step 4: Verify the installation

Check whether Docker is running:

sudo systemctl status docker

Test Docker Engine:

sudo docker run hello-world

Check the Docker Compose version:

docker compose version

If the test container runs successfully and Docker Compose displays its installed version, the installation is ready.

Important: Use the modern docker compose command rather than the legacy standalone docker-compose command. The Compose plugin is installed alongside Docker Engine using the official repository method.

3. Prepare a Production Docker Application

After installing Docker, create a dedicated directory for your application deployment. Keeping application files, configuration files, and deployment settings organized makes ongoing maintenance easier.

Step 1: Create the application directory

Run:

sudo mkdir -p /opt/web-stack/site
sudo chown -R "$USER":"$USER" /opt/web-stack
cd /opt/web-stack

The directory structure will look like this:

/opt/web-stack/
│
├── compose.yaml
│
├── Caddyfile
│
└── site/
    └── index.html

Each file has a specific purpose:

  • compose.yaml defines the Docker application and Caddy services.

  • Caddyfile contains reverse proxy and domain configuration.

  • site/ contains the sample website files.

Step 2: Create a sample web application

For this demonstration, we will use Nginx as the application container. Create an HTML file:

nano /opt/web-stack/site/index.html

Add the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Docker Compose Deployment</title>
</head>
<body>
    <h1>Docker Compose Application Is Running!</h1>
    <p>Successfully deployed using Docker and Caddy on Ubuntu 24.04.</p>
</body>
</html>

Save the file and exit the editor.

This simple HTML page will help you verify that the application is accessible through the reverse proxy. For an actual production deployment, you can replace this sample Nginx application with your own application image.

4. Configure Docker Compose with Persistent Volumes and Restart Policies

Now that the application directory is ready, create the Docker Compose configuration. The configuration will define two services:

  • app – Runs the Nginx application.

  • caddy – Handles incoming HTTP and HTTPS requests and forwards them to the application.

Step 1: Create the Compose file

Open the configuration file:

nano /opt/web-stack/compose.yaml

Add the following configuration:

services:

  app:
    image: nginx:alpine
    restart: unless-stopped

    volumes:
      - ./site:/usr/share/nginx/html:ro

    networks:
      - web

  caddy:
    image: caddy:2
    restart: unless-stopped

    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"

    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

    networks:
      - web

    depends_on:
      - app

networks:
  web:

volumes:
  caddy_data:
  caddy_config:

Let's understand the important configuration options.

Application service: The app service runs the Nginx container. The image parameter specifies the Docker image used to create the container. The restart: unless-stopped policy instructs Docker to restart the container after unexpected exits and Docker daemon restarts, unless the service was explicitly stopped. The volume configuration mounts the website directory from the host server into the Nginx container, and the :ro option makes it read-only.

Caddy service: The Caddy service manages incoming web requests and SSL/TLS certificates. The ports map expose the required traffic (80 for HTTP, 443 TCP for HTTPS, and 443 UDP for HTTP/3).

Persistent Docker volumes: The configuration defines named volumes caddy_data and caddy_config to preserve Caddy's important data and configuration state when containers are recreated. Persistent storage is essential for production Docker deployments. Remember that persistent volumes are not backups; important production data should be backed up to a separate storage location.

5. Install and Configure Caddy as a Docker Reverse Proxy

A reverse proxy receives requests from users and forwards them to the appropriate backend application. In this deployment, Caddy will act as the reverse proxy between visitors and the Nginx application. Instead of exposing the application container directly to the public internet, visitors will connect to Caddy through the server's public IP address and domain name, and Caddy will forward requests through the internal Docker network.

Step 1: Create the Caddy configuration file

Inside the /opt/web-stack directory, create a file named Caddyfile:

nano /opt/web-stack/Caddyfile

Add the following configuration:

example.com {
    encode zstd gzip

    reverse_proxy app:80
}

Replace example.com with your actual domain name. For example, if your domain is mywebsite.com, you can configure both the root and www version like this:

mywebsite.com, www.mywebsite.com {
    encode zstd gzip

    reverse_proxy app:80
}

Step 2: Understand the reverse proxy configuration

The configuration contains three important elements:

  • Domain name: Specifies the hostname that Caddy should handle.

  • Response compression: encode zstd gzip enables supported response compression methods.

  • Reverse proxy destination: reverse_proxy app:80 forwards incoming requests to port 80 of the application container.

Notice that we use app:80 instead of localhost:80. This is important because Docker provides internal DNS resolution for services connected to the same Docker network.

6. Connect Docker Containers Through a Private Network

Docker networking allows multiple containers to communicate without requiring their application ports to be publicly accessible. In our deployment, Caddy and Nginx share a private Docker network called web.

This configuration allows Caddy to access the application through Docker's internal service discovery. When a visitor opens the website, the request follows this process:

  • The visitor requests https://mywebsite.com.

  • The request reaches the server through TCP port 443.

  • Caddy receives the HTTPS request and identifies the requested hostname.

  • The reverse proxy forwards the request to app:80.

  • Nginx processes the request and returns the response.

  • Caddy sends the response back to the visitor over HTTPS.

This separation improves deployment organization and reduces unnecessary public exposure of application services. For a more complex deployment, you can add database, caching, and background worker services to dedicated internal networks.

7. Configure Domain Names and Automatic Free SSL with Caddy

One of the main advantages of using Caddy is its automatic HTTPS functionality. Caddy can obtain and renew publicly trusted TLS certificates for eligible domain names without requiring manual certificate generation or renewal scripts.

Step 1: Configure DNS records

Before starting the deployment, point your domain name to your server's public IP address. Open your domain registrar or DNS management panel and create an A record:

Type Host Value
A @ Your server's public IPv4 address
A www Your server's public IPv4 address

Alternatively, you can configure www as a CNAME pointing to your root domain. Do not create an AAAA record unless the server is actually configured to receive IPv6 traffic.

Step 2: Verify DNS resolution

After saving your DNS changes, connect to your Ubuntu server and check the domain:

dig +short mywebsite.com
dig +short www.mywebsite.com

Step 3: Understand how Caddy obtains SSL certificates

For a standard public website deployment, Caddy identifies the domain, attempts to obtain a trusted TLS certificate, validates control of the domain (via port 80 or 443), and automatically renews it. Because we configured persistent storage for /data, Caddy can retain its certificate data when the container is recreated.

Important: Free SSL does not mean that every domain automatically receives a certificate. Your domain must resolve correctly, the required validation method must be reachable, and Caddy must be able to communicate with the certificate authority.

8. Configure Firewall Rules for Docker and HTTPS Traffic

Firewall configuration is another important part of a secure Docker Compose production deployment. Your server should expose only the services and ports required by the application.

For this example, the primary network requirements are:

Port Protocol Purpose
22 TCP SSH administration (if using the default SSH port)
80 TCP HTTP and certificate validation
443 TCP HTTPS
443 UDP HTTP/3 where supported

Step 1: Allow SSH, HTTP, and HTTPS access

Before enabling UFW, allow your SSH connection to prevent accidental server lockout. Then allow HTTP and HTTPS:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp

Step 2: Enable and verify UFW

sudo ufw enable
sudo ufw status verbose

Important security note: Docker-published container ports can bypass some UFW filtering rules because Docker manages its own packet-filtering rules. Do not assume that allowing or denying a port through UFW alone provides complete control over Docker-published traffic.

9. Start the Docker Compose Application and Test the Deployment

After completing the application, reverse proxy, networking, DNS, and firewall configuration, you can start the deployment.

Step 1: Validate the configuration

cd /opt/web-stack
sudo docker compose config

Step 2: Start the containers and check logs

sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs -f

If the configuration is correct, you can visit https://mywebsite.com in your browser and see the Nginx webpage securely loading via HTTPS.

10. Monitor Docker Logs and Troubleshoot Common Deployment Issues

A production Docker deployment requires regular monitoring to identify application errors, container failures, and network connectivity problems.

Check container status and monitor resources

sudo docker compose ps
sudo docker stats
sudo docker compose logs --tail=100 app

Common Deployment Problems and Solutions

  • Domain name is not accessible: Check DNS records and firewall rules. Ensure ports 80 and 443 are reachable.

  • Caddy cannot obtain an SSL certificate: Review Caddy logs (sudo docker compose logs caddy). Ensure the domain resolves correctly and isn't blocked by another service.

  • 502 Bad Gateway error: Confirm the backend application container is running and mapped correctly via the Docker network (reverse_proxy app:80).

  • Application data disappears: Ensure you are using persistent Docker volumes for essential data rather than writable container filesystems.

  • Containers repeatedly restart: View application logs to discover missing configurations, lack of memory, or internal software crashes.

11. Update and Maintain Your Docker Compose Deployment

Keeping Docker Engine, application images, and Caddy updated is important for maintaining a reliable production environment. To update your deployment safely, follow these steps:

cd /opt/web-stack
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune

Before updating production images, consider testing updates in a staging environment, pinning images to tested versions, backing up data, and monitoring availability closely after redeployment.

12. Why Choose a Dedicated Server for Docker Compose Production Deployment?

For production workloads, a dedicated server provides access to dedicated hardware resources rather than sharing the physical server's CPU and memory with other customers. This is beneficial for applications requiring consistent CPU performance, sufficient RAM, NVMe storage, and reliable network connectivity.

If you are planning to host production Docker applications, explore KW Servers Dedicated Servers to find infrastructure suitable for your workload requirements. Choose a server configuration based on your application's actual CPU, memory, storage, and network requirements.

13. Production Docker Deployment Checklist

Before considering your Docker Compose application ready for production, review the following checklist:

  • Ubuntu 24.04 packages are updated.

  • Docker Engine and Docker Compose are installed correctly and start on reboot.

  • Appropriate container restart policies and persistent volumes are configured.

  • Containers communicate through a private Docker network.

  • Caddy is configured as the reverse proxy, and domain DNS records point to the correct server.

  • Automatic SSL certificate issuance is successful.

  • Unnecessary public container ports are not exposed, and SSH access remains available.

  • Docker logs and resource usage are actively monitored.

Conclusion

Deploying a Docker application involves more than simply running a container. A reliable production environment requires proper container orchestration, reverse proxy configuration, persistent storage, network isolation, HTTPS encryption, and ongoing monitoring.

By combining Docker Compose with Caddy on Ubuntu 24.04, you can manage multiple application services through a single configuration, route incoming traffic through a reverse proxy, and automatically obtain and renew free SSL/TLS certificates.

As your application grows, you can extend the same architecture with database containers, caching services, background workers, and additional application instances. With appropriate server resources, backups, and maintenance practices, Docker Compose can help simplify the operation of production applications.

Discover KW Servers Dedicated Server Locations

KW Servers servers are available around the world, providing diverse options for hosting websites. Each region offers unique advantages, making it easier to choose a location that best suits your specific hosting needs.

Find Your Perfect Server

AI-powered · Instant results

Ask KW Servers AI
Instantly match you to the perfect dedicated server

How can I help you today?

Try asking for specific hardware, locations, or budgets.

Ryzen 9 in Germany

High-performance compute nodes in EU

128GB RAM Servers

Ideal for heavy virtualization

Budget Gaming

Low-latency servers under $100/mo

10TB Storage Arrays

Secure backup and archiving