John Wick

Deploying Python Bots on Docker and a VPS with StayPresent

Not every bot lives on a managed PaaS. Plenty of developers run their python bot docker deployment on their own infrastructure — a VPS, a home server, a Kubernetes cluster — where there's no platform automatically restarting crashed containers or bundling in a health-check dashboard for you. This guide covers both environments: containerizing a bot with Docker, and running it directly on a VPS with systemd, using StayPresent to handle the parts neither environment gives you for free.

Table of Contents

  1. Why Self-Hosted Deployments Are Different
  2. Containerizing a Bot with Docker
  3. Writing the Dockerfile
  4. Health Checks Inside Docker
  5. Running on a Plain VPS
  6. Using systemd for Process Supervision
  7. Full Docker Example
  8. Best Practices
  9. Common Mistakes
  10. FAQs
  11. Conclusion

Why Self-Hosted Deployments Are Different

Managed platforms like Render or Railway restart crashed containers, provide load balancers, and expose a PORT variable automatically. A Docker container on your own VPS, or a bare python main.py running over SSH, gives you none of that by default — if the process dies, it stays dead until something else notices and restarts it.

That "something else" is usually either Docker's own restart policy, systemd, or an application-level supervisor. StayPresent fits into any of these as the layer that manages your bot's subprocess, while the outer layer (Docker, systemd) manages the StayPresent process itself as a fallback.

Containerizing a Bot with Docker

StayPresent works inside Docker exactly like it does anywhere else — it's a standard Python package with no special container requirements.

project/
├── Dockerfile
├── requirements.txt
├── main.py
├── bot.py

Enter fullscreen mode Exit fullscreen mode

Writing the Dockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

Enter fullscreen mode Exit fullscreen mode

requirements.txt should include the production extra:

staypresent[prod]

Enter fullscreen mode Exit fullscreen mode

And main.py looks the same as it would on any other platform:

import os
import staypresent

staypresent.web.json({"status": "running"})

staypresent.run(
    "bot.py",
    port=int(os.getenv("PORT", 8080)),
)

Enter fullscreen mode Exit fullscreen mode

Health Checks Inside Docker

Docker supports a native HEALTHCHECK instruction, which pairs naturally with StayPresent's built-in /health endpoint:

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1

Enter fullscreen mode Exit fullscreen mode

This gives Docker (and orchestrators like Kubernetes or Docker Swarm sitting on top of it) a way to detect an unresponsive container independently of whether your bot process itself has crashed.

Running on a Plain VPS

Without Docker, the same main.py runs directly:

pip install staypresent[prod]
python main.py

Enter fullscreen mode Exit fullscreen mode

The only real difference is that nothing outside your own process will restart it if the entire Python interpreter dies (as opposed to just the bot subprocess crashing, which StayPresent already handles on its own).

Using systemd for Process Supervision

For that outer layer of protection, wrap main.py in a systemd service:

[Unit]
Description=My Bot (StayPresent)
After=network.target

[Service]
WorkingDirectory=/opt/mybot
ExecStart=/usr/bin/python3 main.py
Restart=on-failure
RestartSec=5
Environment=PORT=8080

[Install]
WantedBy=multi-user.target

Enter fullscreen mode Exit fullscreen mode

This creates two layers of recovery: StayPresent restarts your bot subprocess on a crash, and systemd restarts the entire StayPresent process if it ever exits — which, per StayPresent's own design, only happens intentionally or after max_restarts is genuinely exhausted.

Full Docker Example

import os
import staypresent

staypresent.web.json({"status": "running", "env": "docker"})

staypresent.run(
    "bot.py",
    port=int(os.getenv("PORT", 8080)),
    threads=8,
    restart_on_crash=True,
    max_restarts=5,
)

Enter fullscreen mode Exit fullscreen mode

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
HEALTHCHECK --interval=30s CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1
CMD ["python", "main.py"]

Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Let Docker or systemd handle the outer restart layer, and StayPresent handle the inner bot-subprocess layer — they're not redundant, they cover different failure modes.
  • Always EXPOSE the same port StayPresent binds to, and keep the HEALTHCHECK pointed at /health rather than /.
  • On a VPS, run under a dedicated non-root user rather than directly as root.

Common Mistakes

  • Relying on Docker's restart policy alone and setting restart_on_crash=False inside StayPresent — this means every bot crash takes down and restarts the entire container, including the web server, instead of just relaunching the bot script.
  • Forgetting EXPOSE in the Dockerfile. StayPresent will still bind the port correctly inside the container, but without EXPOSE (and a matching -p flag or Compose port mapping) nothing outside the container can reach it.
  • Hardcoding the port in both the Dockerfile and main.py in a way that can drift out of sync — read PORT from the environment in both places.

FAQs

Do I need Docker at all if I already use a VPS?
No — StayPresent works identically with or without a container; Docker just adds portability and an extra restart layer via its own policy.

Does StayPresent work with Docker Compose?
Yes, no special configuration is required beyond a normal service definition pointing at your Dockerfile.

What about Kubernetes?
The same /health endpoint works fine as a liveness or readiness probe target in a Kubernetes deployment spec.

Conclusion

A python bot docker deployment, or a bot running directly on a VPS, doesn't get the built-in restart and health-check conveniences of a managed PaaS — but StayPresent, combined with Docker's HEALTHCHECK or a systemd unit, recreates that same reliability with only a few extra lines of configuration.

pip install staypresent[prod]

Enter fullscreen mode Exit fullscreen mode