Hi Caddy
5 min read
Vercel's developer experience (dx) in the tech industry is truly unrivaled. Their commitment to simplicity and seamless deployment processes sets a new industry standard. Many other tech companies could benefit from adopting this straightforward, developer-centric approach. However, their focus remains on adding more services and features, as well as marketing efforts. While this may benefit the company, it could be detrimental to their core users—developers. Are developers being overlooked? Or is there a disconnect between the company's priorities and the needs of their primary users?
A while back, I was trying to host a small service on Hetzner and map a subdomain to its public IP address by configuring DNS records. Switching between Hetzner's console, my DNS provider's web interface, my terminal, and Windsurf multiple times made me realize how much effort Vercel has invested in creating a smooth and straightforward deployment process. But then again, Vercel is just an AWS wrapper with a hefty markup that's visibly hidden in their 'free' plan. It's not a matter of if, but when you exceed that quite generous offer — then your bill will know no bounds. Or is it the controversy surrounding Vercel's CEO?
Or the fact that you can't use Vercel for all your hosting needs — its strength lies in seamless deployment and optimized performance for specific frameworks. Vercel has limitations. For instance, if you need full control over your server environment, such as configuring specific system settings, installing custom software, or managing security policies, then a Linux server becomes necessary. A Linux server provides granular control over system configurations, resources, and security — offering the flexibility to host complex backend applications, databases, or services that require a customized environment.
While AWS is one of the best cloud providers, it can feel like a complex service jungle, especially if you just want to host a simple service. Google Cloud and Microsoft Azure tell a similar tale, extensive services > dx, steep learning curve with a milder service jungle. In contrast, better options for straightforward hosting are DigitalOcean, Linode, Vultr, OVHcloud, Hetzner, Sevalla, and others. These services aren’t always straightforward on their own; they often seem simple only in comparison to the AWS's of the world. I had to jump through many hoops to configure everything correctly, get it working, and map it to my subdomain. It was a painful process that nearly ruin my holiday. That experience made me realize I need a clear roadmap for my next hosting setup. So, I’m writing this for my future self—and for you.
Prerequisites
- A domain registered with your DNS provider (e.g., example.com)
- A Hetzner Cloud server (Ubuntu/Debian or other Linux distro) with a public IPv4 address
- Docker installed on your server
- Your app dockerized and running on a port (e.g., 3000) in the server
- Access to Hetzner Cloud console and DNS provider's DNS management (e.g., Namecheap).
- SSH access to your server
Adding a Subdomain in Namecheap
- Log into Namecheap.
- Go to Domain List → Manage → Advanced DNS.
- Click Add New Record and enter:
- Type: A Record
- Host: app (this becomes app.example.com )
- Value: your server’s public IPv4 address
- TTL: Automatic (or 30 min)
- Wait 5–30 minutes for DNS propagation.
- Verify the setup by running the code below in your server terminal.
dig +short app.example.com
It should return your public IP.
Install and Configure Caddy with Docker
Create a caddy folder in the root of your project with these two files
Paste this in the Caddyfile and update it with your details
app.example.com {reverse_proxy localhost:3000 }
where localhost should be replaced with your custom container_name, and 3000 should be replaced with your port.
This directs traffic from app.example.com to your app running locally on port 3000.
Caddy will automatically handle HTTPS using Let’s Encrypt.
And your Dockerfile should look like this
# Caddy production image
FROM caddy:alpine
# Copy Caddyfile configuration
COPY Caddyfile /etc/caddy/Caddyfile
# Expose ports for HTTP and HTTPS
EXPOSE 80 443
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/health || exit 1
Docker Compose Setup
Add the following to your `docker-compose.yml` file
services:
caddy:
build:
context: ./caddy
dockerfile: Dockerfile
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
depends_on:
pdf2html:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 5s
networks:
- pdf2html-networkRedeploy
docker compose -f docker-compose.yml up --build -d
Caddy will automatically:
- Provisions an SSL certificate for app.example.com .
- Redirects HTTP to HTTPS 😀.
- Keeps your certificate renewed automatically.
Verify
Confirm your app is accessible:
Check status of the issued SSL certificate inside the container:
docker exec -it caddy caddy list-certificates
Wanna add more?
To add more domains or subdomains, for example api.example.com, add the following into the Caddyfile, previously created.
api.example.com {reverse_proxy localhost:4000}
Add the corresponding api A record in Namecheap pointing to the same server.
Gotchas
- Most services, including GitHub, still primarily use IPv4. Make sure to allocate and pay for a public IPv4 address
- When configuring your firewall or security groups, only open the ports necessary for your application, aside from the standard ports 80 (HTTP) and 443 (HTTPS). See screenshot below.
- Use key-based authentication instead of passwords, disable root login, and consider changing default SSH ports.
- Monitor your usage to avoid unexpected charges, especially with bandwidth or additional IP addresses.
- Keep security in mind, regularly update your configurations, and consider scalability options as your project grows.
Hosting a small app or service can be straightforward with the right tools and a clear roadmap. While achieving the ease of deployment that platforms like Vercel offer may require setting up a CI/CD pipeline, it's important to consider whether adding such complexity is necessary at this stage. Overengineering early on can lead to unnecessary complications, increased maintenance & cost, and slowed progress.
Keep creating and keep building.