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 upgrade -y
Install the required packages:
Step 2: Add the official Docker repository
Create a directory for the Docker repository signing key:
Download the official Docker GPG key:
Set the appropriate permissions:
Next, add the official Docker APT repository:
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:
Step 3: Install Docker Engine and Compose
Install Docker Engine, Docker CLI, containerd, Buildx, and the Docker Compose plugin:
Enable Docker to start automatically when the server boots:
Step 4: Verify the installation
Check whether Docker is running:
Test Docker Engine:
Check the 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 chown -R "$USER":"$USER" /opt/web-stack
cd /opt/web-stack
The directory structure will look like this:
│
├── compose.yaml
│
├── Caddyfile
│
└── site/
└── index.html
Each file has a specific purpose:
compose.yamldefines the Docker application and Caddy services.Caddyfilecontains 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:
Add the following content:
<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:
Add the following configuration:
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:
Add the following configuration:
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:
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 gzipenables supported response compression methods.Reverse proxy destination:
reverse_proxy app:80forwards 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 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 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
Step 2: Enable and verify UFW
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
sudo docker compose config
Step 2: Start the containers and check logs
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 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:
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.
KW Servers Recommended Tutorials
Php, Control Panel, Linux, Dedicated Server, Web
How to Use MultiPHP INI Editor in WHM?
Master the MultiPHP INI Editor in WHM to customize PHP settings per version. This step-by-step guide helps you optimize performance and manage directives with ease using Basic and Editor modes.
Plesk, Control Panel, Web, Network
How to Set NS (Nameserver) Records in Plesk – Step-by-Step Guide
Learn how to configure NS (Nameserver) records in Plesk for seamless DNS management. This guide covers setup, best practices, and troubleshooting to ensure optimal domain resolution and performance.
Control Panel, Dedicated Server, Web, Mysql, Security
What is cPanel? The Complete Guide to Dedicated Server Management (2026)
Master cPanel on your dedicated server. Learn file management, DNS config, email setup, database administration, and security features in this 2026 guide by KW Servers.
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.