DEV Community

Pavel
Pavel

Posted on • Originally published at hostim.dev

How to Self-Host a Docker Compose App

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
Enter fullscreen mode Exit fullscreen mode

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

  • 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
Enter fullscreen mode Exit fullscreen mode
  • Install Docker packages:
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enter fullscreen mode Exit fullscreen mode

2. Clone your project

We'll use a demo Django app

git clone https://github.com/hostimdev/demo-django.git
cd demo-django
Enter fullscreen mode Exit fullscreen mode

💡 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

nano /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Test and restart nginx:

nginx -t
systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

Install Certbot and request an HTTPS certificate with automatic redirect:

apt install certbot python3-certbot-nginx -y
certbot --nginx # follow the instructions
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Edit your nginx config:

nano /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Enable and start it:

systemctl enable myapp
systemctl start myapp
Enter fullscreen mode Exit fullscreen mode

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

🎥 Here's a 27-second demo of deploying with Hostim.dev:

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.

Top comments (0)