Caddy is a modern, open-source web server written in Go, known for its automatic HTTPS, simple configuration, and powerful reverse proxy capabilities. Itβs designed to be developer-friendly and production-ready out of the box.
sudo apt update && sudo apt upgrade -ycurl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh
sudo apt install docker-compose-pluginsudo usermod -aG docker $USERsudo systemctl enable dockersudo rebootdocker run hello-worldThis guide will walk you through hosting two seperate websites.
mkdir ~/docker mkdir ~/docker/caddy mkdir ~/docker/caddy/website1 # This directory will hold all the files for website 1 mkdir ~/docker/caddy/website2 # This directory will hold all the files for website 2
In ~/docker/caddy create docker-compose.yml I setup two websites. You can strip this down to one if you'd like. Or add more.
services:
caddy:
image: caddy
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./website1:/srv/website1 # Content for the first website
- ./website2:/srv/website2 # Content for the second website
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
In ~/docker/caddy create Dockerfile
FROM caddy:builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare
FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
In ~/docker/caddy create Caddyfile
website1.com {
root * /srv/website1
file_server
encode gzip
tls {
issuer acme {
dns cloudflare {
api_token "API-Token-from-Cloudflare"
}
}
}
}
website2.com {
root * /srv/website2
file_server
encode gzip
tls {
issuer acme {
dns cloudflare {
api_token "API-Token-from-Cloudflare"
}
}
}
}
I use Cloudflare so I'll walk through that. I also assume that you've secured two domains: website1.com and website2.com
curl ifconfig.me.Since your IP address can change, it's important to update Cloudflare periodically
~/docker/caddy create update_website1.sh
#!/usr/bin/env python3
import requests
# === CONFIGURATION ===
ZONE_NAME = "website1.com"
RECORD_NAME = "website1.com"
API_TOKEN = "APT-TOKEN-FROM-CLOUDFLARE"
# === FUNCTIONS ===
def get_public_ip():
return requests.get("https://api.ipify.org").text.strip()
def get_zone_id():
headers = {"Authorization": f"Bearer {API_TOKEN}"}
r = requests.get("https://api.cloudflare.com/client/v4/zones", headers=headers)
zones = r.json()["result"]
for zone in zones:
if zone["name"] == ZONE_NAME:
return zone["id"]
raise Exception("Zone not found")
def get_record_id(zone_id):
headers = {"Authorization": f"Bearer {API_TOKEN}"}
url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records"
r = requests.get(url, headers=headers)
records = r.json()["result"]
for record in records:
if record["name"] == RECORD_NAME and record["type"] == "A":
return record["id"]
raise Exception("A record not found")
def update_dns_record(zone_id, record_id, ip):
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{record_id}"
data = {
"type": "A",
"name": RECORD_NAME,
"content": ip,
"ttl": 300,
"proxied": False
}
r = requests.put(url, headers=headers, json=data)
return r.json()
# === MAIN EXECUTION ===
if __name__ == "__main__":
try:
ip = get_public_ip()
zone_id = get_zone_id()
record_id = get_record_id(zone_id)
result = update_dns_record(zone_id, record_id, ip)
print(f"[β] Updated {RECORD_NAME} to {ip}")
print("Cloudflare response:", result)
except Exception as e:
print(f"[β] Error: {e}")
sudo apt update && sudo apt upgrade -ysudo apt install python3 python3-pip -y
crontab -e*/10 * * * * /yourdirectory/update_website1.sh >> /yourdirectory/website1-ddns.log 2>&1
index.html and paste this into it
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Website1<title>
<head>
<body>
<h1>This is website1<h1>
<body>
<html>
index.html and paste this into it
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Website2<title>
<head>
<body>
<h1>This is website1<h1>
<body>
<html>
From within ~/docker/caddy run:
cd ~docker/caddy
docker compose build
docker compose up -d
docker logs caddy
Open your browser and go to website1.com and then try website2.com
If this doesn't work or you're having difficulty, feel free to contact me. Also, if you find any errors, or ways to imporove on this, let me know.This is fairly complicated and there are a lot of moving parts.
Now that your website is up, you can add analytics to it by installing kaunta . You can also use Google Analytics.
If you find my content useful, please consider supporting this page: