You've built the thing. It works locally. Now you want it on the internet with a real URL and HTTPS, not http://1.2.3.4:3000 and not a 40-character deployment platform URL.
This tutorial walks you through the fastest path: a VPS, Docker, and DomainDock. Everything from the blank server to yourproject.digitalweb.host in under 5 minutes.
What You'll Need
- A VPS with a public IP address (any provider works: Hetzner, DigitalOcean, Vultr, etc.)
- Docker installed on the VPS
- A DomainDock account (free, no credit card)
That's it. No domain registrar. No Cloudflare account. No SSL configuration on your end.
Minute 1: Start Your App on the VPS
SSH into your VPS and get your app running. For this tutorial, we'll use a Node.js app as an example. The steps are identical for any language or framework.
If your app is already running, skip to Minute 2.
Option A: Docker (recommended)
docker run -d \
--name myapp \
--restart unless-stopped \
-p 127.0.0.1:3000:3000 \
your-docker-image:latest
Binding to 127.0.0.1:3000 exposes the port only on loopback. Traefik (added in the next step) will proxy traffic to it. Your app is never directly exposed on port 3000 to the internet.
Option B: Direct process
node server.js &
# or
pm2 start server.js --name myapp
Make sure the app listens on localhost:3000 (or whatever port you choose).
Verify it's responding:
curl http://localhost:3000
If you get HTML or JSON back, you're good.
Minute 2: Start Traefik
Traefik is the reverse proxy that handles HTTPS and domain routing. Run it with Docker:
mkdir -p ~/traefik/letsencrypt ~/traefik/dynamic
touch ~/traefik/letsencrypt/acme.json
chmod 600 ~/traefik/letsencrypt/acme.json
Create ~/traefik/traefik.yml:
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: you@example.com
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
providers:
file:
directory: /etc/traefik/dynamic
watch: true
Create ~/traefik/docker-compose.yml:
services:
traefik:
image: traefik:v3.3
ports:
- "80:80"
- "443:443"
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./dynamic:/etc/traefik/dynamic:ro
- ./letsencrypt:/letsencrypt
restart: unless-stopped
network_mode: host
Using network_mode: host lets Traefik reach processes on 127.0.0.1:3000 without any extra Docker networking configuration.
Start Traefik:
cd ~/traefik && docker compose up -d
Traefik is now running. It's watching port 80 and 443, and watching the dynamic directory for routing rules.
Minute 3: Claim Your Domain on DomainDock
- Go to DomainDock and create a free account
- Click New project and give it a name
- Click Add domain
- Enter your domain prefix (this becomes
yourprefix.digitalweb.host) - Enter your VPS IP address
- Enter port
3000(or whatever port your app uses) - Click Create
DomainDock creates a Cloudflare DNS A record pointing yourprefix.digitalweb.host to your VPS IP.
Within 30–60 seconds, the DNS is globally propagated.
Minute 4: Write the Traefik Routing Rule
Create ~/traefik/dynamic/myapp.yml on your VPS:
http:
routers:
myapp:
rule: "Host(`yourprefix.digitalweb.host`)"
service: myapp-svc
entryPoints:
- websecure
tls:
certResolver: letsencrypt
services:
myapp-svc:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
Replace yourprefix with the prefix you chose in DomainDock.
Traefik detects the new file immediately (no restart needed) and:
- Starts routing requests for
yourprefix.digitalweb.hostto127.0.0.1:3000 - Requests a Let's Encrypt certificate for
yourprefix.digitalweb.host - Stores the certificate and starts serving HTTPS
Minute 5: Test It
Open your browser and navigate to https://yourprefix.digitalweb.host.
If everything is correct, you'll see your app with a valid HTTPS certificate in the browser.
If it's not working yet, wait 30 more seconds. Certificate issuance occasionally takes a moment on the first request.
Troubleshooting
502 Bad Gateway
Traefik is routing but can't reach your app. Check that your app is actually running: curl http://localhost:3000. If it responds, verify the port in your routing rule matches.
404 from Traefik
The routing rule isn't being picked up. Check that the file is in ~/traefik/dynamic/ and the YAML syntax is valid: docker exec traefik traefik healthcheck.
Certificate not issued
Let's Encrypt needs port 80 to be reachable from the internet for the HTTP-01 challenge. Check your VPS firewall: ufw allow 80 and ufw allow 443.
DNS not resolving
Wait a bit longer, or check with dig yourprefix.digitalweb.host A from your local machine. If it returns your VPS IP, DNS is correct and the issue is elsewhere.
What You Just Set Up
In under 5 minutes, you deployed an app with:
- A clean custom domain:
yourprefix.digitalweb.host, globally routable - Automatic HTTPS: Let's Encrypt certificate, auto-renewed by Traefik
- HTTP to HTTPS redirect: anyone who visits over HTTP is redirected automatically
- Health monitoring: DomainDock is already running health checks on your domain
- Branded error page: if your app goes down, visitors see a clean status page instead of a browser error
Scaling Up
This setup handles one app. To add more:
- Start another app on a different port (e.g.,
127.0.0.1:4000) - Create another domain in DomainDock with the same VPS IP, port 4000
- Add
~/traefik/dynamic/anotherapp.ymlwith the corresponding routing rule
The same Traefik instance handles all of it. No restarts. No new certificates. Each domain gets its own Let's Encrypt cert automatically.
Your project deserves a real address. Create your free domain on DomainDock.