# Containerizing Python Apps

> How to containerize Python scripts with Docker, deploy via Portainer, and add uptime monitoring.

By Zsolt Bizderi · Published 2025-04-23
Canonical: https://ambientnode.uk/containerizing-python-apps

I have an extensive Python script that I currently run in a dev environment. Normally, I’d use [n8n](https://n8n.io/) to automate workflows like this, but n8n’s Python node runs Pyodide, which is a WebAssembly-based Python interpreter. That’s fine for light tasks but it doesn’t support many critical features, including:

* File system access
* Custom dependencies (e.g. `psycopg2`, `pandas`, etc.)
* Making raw `requests` with custom headers
* Multithreading or subprocess usage

All of which my script leverages. Because of these limitations, I decided to containerize the script, deploy it through Portainer, and monitor it with Uptime Kuma. It’s a very clean and repeatable way to deploy Python apps on any Linux box or VM.

### 1. Project Structure

Create a project folder on the server, in this case we’ll call it `python-script`:

```
mkdir python-script && cd python-script
```

Inside this folder, create:

python-script/  
├── Dockerfile  
├── requirements.txt  
└── app.py

---

### 2. Dockerfile

```
FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the script
COPY app.py .

# Run it
CMD ["python", "app.py"]
```

> Change the Python version tag if needed (e.g., `3.10-slim` or `3.12-slim`).

---

### 3. Build the Docker image

From inside the project folder:

```
docker build -t python-script:latest .
```

---

### 4. Deploy it with Portainer

You can absolutely run this with Docker Compose locally or as a standalone container. But I used Portainer so I could monitor logs more easily from the web UI.

In Portainer:

1. Go to **Stacks > Add Stack**
2. Give it a name like `python-script`
3. Paste in the following YAML:

```
version: '3.8'

services:
  python_script:
    image: python-script:latest
    container_name: python-script
    restart: always
    command: python -u app.py  # -u forces unbuffered output so logs show up live in Portainer
```

> Without `-u`, Docker will buffer stdout and your `print()` logs won’t show up in Portainer until the buffer flushes (which might never happen if your script runs indefinitely).

---

### 5. Monitor with Uptime Kuma

If your script runs continuously or at regular intervals, you can:

* Add a heartbeat call to [Healthchecks.io](https://healthchecks.io/) or a similar service
* Or expose a simple `/health` endpoint via Flask or FastAPI inside the container and point Uptime Kuma at it
* Monitor the container itself with Uptime Kuma's Docker Container monitor.

For example, to use a basic heartbeat with [Healthchecks.io](https://healthchecks.io):

```
import requests

requests.get("https://hc-ping.com/YOUR-UUID/start")

# your logic...

requests.get("https://hc-ping.com/YOUR-UUID")
```

---

### Wrap-up

This method is lightweight and works great for:

* Data ETL jobs
* API polling scripts
* Scheduled sync tasks
* Anything you’d otherwise try to cram into cron or n8n
