Skip to main content

How to Self-Host a Docker Compose App

· 7 min read

You've got a working docker-compose.yml, and now you want to put it online.

Maybe it's a SaaS side project. A personal site. A dashboard for a client. Whatever it is – you're here because you want to host a Compose app, and you don't want to spend hours fiddling with YAML, CI pipelines, or Kubernetes manifests.

Let's walk through what it really takes to host a Docker Compose project on your own. And then I'll show you what I built to make this process go away – for myself and anyone else who's tired of copy-pasting configs.


Example: We'll use this project as our demo: hostimdev/demo-django (A simple Django app with MySQL and Redis)

🧱 The Hard Way: VPS + Docker + Compose​

First, the classic method. Take your favorite VPS provider (we like Hetzner – in fact, Hostim.dev runs on their bare metal servers), and spin up a server.

Note: This guide assumes you're logged in as root. If not, prefix commands with sudo or use sudo -i to switch to root.

1. Provision the VPS​

Pick a Linux image (Ubuntu or Debian), log in via SSH, and update your system:

apt update && apt upgrade -y

Install Docker and docker-compose (we follow the official guide):

  1. Set up Docker's apt repository.
# Add Docker's official GPG key:
apt-get update
apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
  1. Install Docker packages:
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

2. Clone your project​

We'll use a demo Django app

git clone https://github.com/hostimdev/demo-django.git
cd demo-django

💡 Private repo? You can:

⚠️ If you go with the SSH method, make sure to secure the private key on the VPS and limit server access. It's secure if your system is.


3. Deploy your app​

Assuming you have a docker-compose.yml ready:

docker compose up -d

That runs the app. But there are some problems:

  • It won't restart after reboot.
  • There's no HTTPS.
  • You're exposing raw ports to the world.

Security Note: To prevent exposing services to the public internet, modify your docker-compose.yml to bind ports only to localhost. For example:

ports:
- "127.0.0.1:8000:8000"

This ensures services are only accessible from the local machine, not directly from the internet.


4. Add HTTPS with nginx​

Install nginx:

apt install nginx -y

Change the default config to proxy traffic to your app (assumes it runs on port 8000):

nano /etc/nginx/sites-available/default

Replace the location / block inside the server {} with:

location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

Test and restart nginx:

nginx -t
systemctl restart nginx

Install Certbot and request an HTTPS certificate with automatic redirect:

apt install certbot python3-certbot-nginx -y
certbot --nginx # follow the instructions

It will configure the https for you

🔒 Bonus: Block direct access to your server's IP

By default, if someone enters your server's IP address in a browser, nginx may respond with your app or a default welcome page. To prevent this and serve content only under your domain, block IP-based access.

Install ssl-cert package, we need it just for dummy certs:

app install ssl-cert -y

Edit your nginx config:

nano /etc/nginx/sites-available/default

Replace the topmost server block (usually the default one on port 80) with this:

# Block HTTP requests to IP (default server)
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;

return 444;
}

Then add this just below to block HTTPS access by IP:

# Block HTTPS requests to IP (default server)
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

return 444;
}

⚠️ Replace the cert paths with your real SSL cert/key if you're not using the default snakeoil test cert.

Restart nginx:

nginx -t
systemctl restart nginx

From now on, only your domain name will serve content. Requests to the raw IP will be silently dropped (code 444 = connection closed with no response).


5. Make it survive reboots​

Stop the current stack before setting up systemd:

docker compose down

Then create a systemd unit:

# /etc/systemd/system/myapp.service
[Unit]
Description=My Docker Compose App
After=network.target

[Service]
Type=oneshot
WorkingDirectory=/root/demo-django
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Enable and start it:

systemctl enable myapp
systemctl start myapp

Your app will now automatically start after reboots.


6. Handle volumes, backups, logs…​

If your Compose file uses volumes (e.g. MySQL, Redis), you now have to:

  • Make sure volume paths are persisted and backed up
  • Inspect logs manually or add a logging layer (e.g. Loki or Graylog)
  • Possibly add monitoring for CPU, RAM, or disk usage

😮‍💨 It's a Lot​

Even if you're comfortable with the CLI, this gets repetitive fast:

  • Every project = new VPS
  • Manual nginx tweaks
  • No dashboard, no metrics
  • No easy way to share access

After doing this too many times for clients, side projects, and demos, I decided to build something that just… does it for me.


🧃 The Easy Way: Paste & Deploy​

I'm building Hostim.dev – a developer-first platform that lets you paste your docker-compose.yml, click "Deploy", and you're live.

Well, unless you docker-compose.yml is crazy big with tons of services, then it might take some manual configuration.

Here's what it takes:

  1. Sign up (no credit card required)
  2. Create a project and paste your Compose file
  3. We generate your stack – apps, databases, volumes
  4. Logs, metrics, and HTTPS just work

No nginx. No SSH. No firewalls. Just a real app online.


🧪 Try It Free​

Every new user gets a 5-day trial project, plus always-free (albeit small) tiers for:

  • MySQL and Postgres
  • Redis
  • Persistent volumes

If you're tired of fighting servers and YAML, check it out:

👉 Get started with Hostim.dev

🚀 Hostim.dev is currently in closed beta – if you want early access, join the waitlist or email me at pv@hostim.dev


P.S. If you do enjoy the ops side – I get it. I used to too. But these days, I just want to ship faster. That's what this is all about.