IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Docker Health Checks for VPS Deployments in 2026

By Raman Kumar

Share:

Updated on Aug 12, 2026

Docker Health Checks for VPS Deployments in 2026

Start with a container that tells you when it is healthy

Docker health checks give you a clear signal that your app is ready, still working, and safe to keep behind a reverse proxy. On a small VPS, that matters because uptime problems often show up first as slow responses, failed migrations, or a container that is running but not serving traffic. If you are planning a fresh deployment on Hostperl VPS hosting, health checks are one of the simplest ways to catch those failures before your customers do.

This tutorial walks through a complete setup on a fresh server: SSH login, OS detection, non-root access, Docker installation, a sample app with health checks, Docker Compose, Nginx reverse proxying, firewall rules, and final verification. If you want background reading on deployment choices, Hostperl also has a practical Docker app deployment guide and a Git-Based Docker deployment guide that fit the same operational workflow.

What you will build

You will deploy a small web app in Docker with a proper HEALTHCHECK, publish it through Nginx, and confirm that unhealthy containers are visible from both Docker and the browser. The example uses:

  • Ubuntu/Debian or AlmaLinux/Rocky Linux
  • Docker Engine and Docker Compose
  • A non-root admin user called deploy
  • Application files in /opt/myapp
  • Port 8000 for the app and port 80 for Nginx

If your customer work involves staging sites, launch cutovers, or agency-managed sites, this approach is safer than running an app container without a health endpoint. It also pairs well with Hostperl’s migration and support workflows for teams that need predictable recovery, not just a container that starts once.

Connect to the VPS and check the operating system

On your local computer:

ssh root@203.0.113.10

Replace 203.0.113.10 with the real public IP assigned to your server. This reserved address is only an example.

If your provider gives you a default non-root login, use it instead:

ssh deploy@203.0.113.10

After you log in, detect the OS before you run distribution-specific commands:

cat /etc/os-release

You should see either Ubuntu/Debian fields or AlmaLinux/Rocky Linux fields. Keep the first root session open until the new sudo user login is tested.

Create a non-root sudo user

On the VPS as root:

Ubuntu and Debian

adduser deploy

Set a password when prompted, then grant sudo access:

usermod -aG sudo deploy

Create the SSH directory, then copy your public key from the root account if one already exists:

mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

AlmaLinux and Rocky Linux

useradd -m deploy
passwd deploy
usermod -aG wheel deploy
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

Now open a second terminal on your local computer and test the new login:

ssh deploy@203.0.113.10

Then test sudo from the new session:

sudo -v

If that works, keep using the deploy account for the rest of the tutorial. Only after the new login is confirmed should you consider tightening root access later.

Update packages and install Docker

On the VPS as the non-root sudo user:

Ubuntu and Debian

sudo apt update
sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg

Install Docker from the official repository so you get current packages and Compose support:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

AlmaLinux and Rocky Linux

sudo dnf -y update
sudo dnf -y install dnf-plugins-core ca-certificates curl
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Enable Docker and confirm the version:

sudo systemctl enable --now docker
docker --version
docker compose version

You should see Docker Engine and Docker Compose plugin versions. If Docker cannot start, check the service immediately with systemctl status docker before moving on.

Open the firewall before you publish the app

Do this before you remove any access path. You need SSH, HTTP, and later HTTPS.

Ubuntu and Debian with UFW

sudo apt -y install ufw
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

AlmaLinux and Rocky Linux with firewalld

sudo dnf -y install firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

If SELinux is enforcing on AlmaLinux or Rocky Linux, keep it that way for now. We will use standard ports, so you should not need a policy change.

Build the app with a health endpoint

For this tutorial, use a small Python app because it keeps the health logic easy to see. The same pattern works for Node.js, Java, or PHP apps that sit behind Nginx.

On the VPS as the non-root sudo user:

sudo mkdir -p /opt/myapp/app
sudo chown -R deploy:deploy /opt/myapp

Create the application file:

cd /opt/myapp/app
cat > app.py <<'EOF'
from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/health":
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"ok")
        else:
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"Hello from Docker health checks\n")

HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
EOF

Create the Dockerfile with a health command:

cat > Dockerfile <<'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY app.py /app/app.py
EXPOSE 8000
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 CMD curl -fsS http://127.0.0.1:8000/health || exit 1
CMD ["python", "/app/app.py"]
EOF

Create the Compose file:

cat > /opt/myapp/docker-compose.yml <<'EOF'
services:
  app:
    build: ./app
    container_name: myapp
    ports:
      - "127.0.0.1:8000:8000"
    restart: unless-stopped
EOF

The app only binds to 127.0.0.1. Nginx will be the public entry point.

Start the container and check health status

On the VPS as the non-root sudo user:

cd /opt/myapp
sudo docker compose up -d --build

Check that Docker sees the container as healthy after a short delay:

sudo docker ps
sudo docker inspect --format='{{.State.Health.Status}}' myapp

You want healthy. If you see starting, wait 10-20 seconds and run the inspect command again. If you see unhealthy, jump to the troubleshooting section later in this guide.

Confirm the app responds locally on the VPS:

curl -i http://127.0.0.1:8000/
curl -i http://127.0.0.1:8000/health

You should get an HTTP 200 response and the body ok from the health path.

Put Nginx in front of the container

On the VPS as the non-root sudo user:

Ubuntu and Debian

sudo apt -y install nginx

AlmaLinux and Rocky Linux

sudo dnf -y install nginx

Create a reverse proxy config that forwards traffic to the container:

sudo tee /etc/nginx/conf.d/myapp.conf > /dev/null <<'EOF'
server {
    listen 80;
    server_name server.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        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;
    }
}
EOF

Test the configuration before you reload Nginx:

sudo nginx -t

If the syntax is correct, enable and reload the service:

sudo systemctl enable --now nginx
sudo systemctl reload nginx

Replace server.example.com with your real hostname after DNS points to the VPS. For now, you can test with the server IP from the client side.

Add HTTPS with Let’s Encrypt after DNS is ready

Once your domain points to the server and port 80 is reachable, issue a certificate.

Ubuntu and Debian

sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

AlmaLinux and Rocky Linux

sudo dnf -y install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Replace example.com with your real domain. Certbot should update Nginx automatically and install a renewal timer. Confirm it with:

sudo certbot renew --dry-run

For hosting customers, this is the point where support tickets usually drop. Once HTTPS is live, browser warnings and redirect mistakes stop becoming launch blockers.

Test Docker health checks from the browser and the server

On the VPS:

sudo docker logs myapp --tail 50
sudo systemctl status nginx --no-pager
sudo ss -tulpn | grep -E ':80|:8000'

On your local computer:

curl -I http://203.0.113.10
curl http://203.0.113.10/health

Replace 203.0.113.10 with your public IP if you are not using DNS yet. You should see a 200 response from Nginx and the ok body through the proxy.

If you are using a browser, load the site and confirm that the response comes from the container, not the default Nginx page.

Make the deployment survive a reboot

Restart the server only after you have confirmed the app, Nginx, and Docker are all running.

On the VPS as the non-root sudo user:

sudo reboot

After the VPS comes back, log in again and run:

sudo systemctl status docker --no-pager
sudo systemctl status nginx --no-pager
cd /opt/myapp && sudo docker compose ps
sudo docker inspect --format='{{.State.Health.Status}}' myapp

You want Docker and Nginx active, the container running, and the health state still healthy. That tells you the app will come back cleanly after a maintenance reboot.

If you want to run Docker workloads on a VPS without guessing about the platform underneath, Hostperl keeps the setup practical: fast provisioning, responsive support, and room to grow when the app stops being a side project. A Hostperl VPS is a good fit when you want full control, while teams with heavier traffic can move to a dedicated server later without changing the deployment pattern.

That matters for agencies, SaaS launch teams, and small businesses that need predictable recovery more than clever infrastructure.

Troubleshooting the most common failures

Container shows unhealthy

Run:

sudo docker inspect myapp --format='{{json .State.Health}}'
sudo docker logs myapp --tail 100

If the health command cannot reach /health, check the app code and confirm port 8000 is listening inside the container.

Nginx returns 502 Bad Gateway

Check the proxy target and whether the container is alive:

sudo docker ps
curl -i http://127.0.0.1:8000/health
sudo tail -n 50 /var/log/nginx/error.log

If the local curl fails, restart the app container with sudo docker compose up -d --build.

Firewall blocks the site

Confirm the rules are active:

sudo ufw status verbose
sudo firewall-cmd --list-all

Open port 80 and 443 if needed, then retest from your computer.

Certbot cannot issue a certificate

Check DNS and port 80 reachability:

dig +short example.com
curl -I http://example.com

If DNS points elsewhere or port 80 is blocked, fix that first. Certbot will not complete until the server answers publicly.

When to use this setup

This pattern works well for client sites, internal tools, demo apps, and small SaaS deployments that need a simple reliability signal. It is also a good fit when you want support teams to diagnose problems quickly: Docker shows the container state, Nginx handles public traffic, and the health endpoint gives you a fast yes-or-no answer.

For more background on deployment choices, you may also find Hostperl’s reverse proxy guide for Node.js, Python, and Java apps useful, especially if your app is not Python-based. If you are comparing runtime and database planning, the Docker Compose deployment article is a solid next step.

FAQ

Do Docker health checks replace monitoring?

No. They tell Docker whether the container is healthy, but you should still monitor HTTP response, disk space, memory use, and restart counts.

Can I use this with Node.js or Java instead of Python?

Yes. Keep the same layout: app on localhost, Nginx on port 80 or 443, and a health endpoint that returns HTTP 200 quickly.

Should I expose port 8000 publicly?

No. This tutorial binds the app to 127.0.0.1 so only Nginx can reach it from outside.

How do I know the container will survive a reboot?

Use restart: unless-stopped, enable Docker and Nginx with systemd, and test a reboot before the site goes live.

What if I need to move this to a bigger server later?

The Compose file, Nginx config, and health endpoint all move cleanly. That makes upgrades easier when your traffic grows or you shift to a stronger Hostperl plan.