|

Configura 5 unidades systemd para tu app Python

Systemd es una forma más simple y eficiente de manejar múltiples aplicaciones Python en un VPS económico, consumiendo apenas ~70 MB de RAM total para 5 servicios sin necesidad de Docker. Un artículo de dev.to publicado el 10 de abril de 2026 presenta 5 archivos .service copy-paste que incluyen API server, collector en background, healthcheck, alerts worker y dashboard supervisión, todos iniciados automáticamente en boot con systemctl enable.

En 30 segundos

  • Systemd maneja 5 servicios Python independientes en un VPS de $5/mes consumiendo ~70 MB RAM total
  • Cada servicio es un archivo .service en /etc/systemd/system/ con [Unit], [Service] e [Install]
  • Reintentos automáticos, logs centralizados con journalctl, y arranque automático en boot sin orquestación extra
  • Los 5 servicios típicos son: API server (Flask/Gunicorn), background collector loop, healthcheck monitor, alerts worker, dashboard supervisión
  • Mejor que Docker para proyectos pequeños: menos abstracciones, menos overhead, configuración más legible y debuggeable

Qué es systemd y por qué es mejor que Docker para proyectos pequeños

Systemd es el gerenciador de servicios nativo de Linux (presente en Ubuntu, Debian, CentOS, Fedora desde hace años). Es un init system que controla cuándo arrancan procesos, cómo se reinician si fallan, dónde guardan sus logs, y cómo compartir variables de entorno entre múltiples servicios.

Docker es útil cuando necesitás reproducibilidad exacta entre máquinas, versionado de dependencias, o segregación de redes. Pero para un proyecto personal en tu VPS de $5/mes donde vos sos el único que mantienes el código (ponele que un scraper de noticias, una API interna, o un collector de métricas), systemd es más simple, menos consumidor de RAM y más transparente. Con systemd ves exactamente qué hace cada proceso, qué variables de entorno tiene, cuándo se reinicia, y dónde están los logs. Con Docker agregás una capa de complejidad que probablemente no necesites.

La prueba: el artículo de dev.to del 10 de abril de 2026 reporta 5 servicios Python corriendo en paralelo, consumiendo un total de ~70 MB de RAM en resident memory, arriba por más de 24 horas sin problemas en un VPS Ubuntu stock. (Sin Docker, sin Kubernetes, sin orquestación.) Eso no es poco.

Requisitos previos: qué necesitás para arrancar

Prácticamente nada especial. Si ya tenés un VPS con Ubuntu 20.04+ o Debian 11+ (que viene con systemd), estás listo. Necesitás:

  • Ubuntu/Debian VPS con acceso sudo. Cualquiera de donweb.com vale, o DigitalOcean, Linode, Hetzner, lo que sea.
  • Python 3.8+ (que ya viene en cualquier distro moderna).
  • Un virtual environment para tu proyecto (pip/venv estándar).
  • Editor de texto (vim, nano, o editar en local y scp).

El setup es: creás una carpeta del proyecto, le metés un venv, instalás tus dependencias con pip, y luego creás 5 archivos .service en /etc/systemd/system/. Eso es todo. No hay docker build, docker-compose, Watchtower, ni nada de eso.

Estructura de un archivo .service: las tres secciones clave

Cada archivo systemd es un archivo de texto simple con tres bloques: [Unit], [Service] e [Install].

[Unit] — metadatos del servicio.

  • Description= — descripción legible (aparece en logs y systemctl status)
  • After= — qué servicios deben arrancar antes que este (ejemplo: After=network.target para servicios que necesitan red)

[Service] — configuración de ejecución.

  • Type=simple — el proceso se mantiene en foreground (lo standard para Python)
  • User= — usuario que corre el servicio (no root, por seguridad)
  • WorkingDirectory= — dónde systemd cambia de directorio antes de ejecutar ExecStart
  • ExecStart= — el comando exacto que arranca el servicio (ej: /usr/bin/python3 /root/project/api.py)
  • Restart=always — reinicia automáticamente si el proceso muere
  • RestartSec=5 — espera 5 segundos antes de reintentar
  • StandardOutput=append: y StandardError=append: — redirige stdout/stderr a un archivo log

[Install] — cómo arranca el servicio en boot.

  • WantedBy=multi-user.target — arranca cuando el sistema llega al multi-user runlevel (después del boot, antes de que el usuario loguee)

La plantilla básica copy-paste:

[Unit]
Description=Mi App Python
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/home/deploy/myapp/venv/bin/python api.py
Restart=always
RestartSec=5
StandardOutput=append:/home/deploy/myapp/logs/api.log
StandardError=append:/home/deploy/myapp/logs/api.log

[Install]
WantedBy=multi-user.target

Una vez que guardás el archivo (ej: /etc/systemd/system/myapp-api.service), le decís a systemd que lo lea, y luego lo activás: Cubrimos ese tema en detalle en ejecutar agentes sin depender de APIs.

sudo systemctl daemon-reload
sudo systemctl enable --now myapp-api

enable --now hace dos cosas: enable lo deja configurado para arrancar en boot, y --now lo arranca inmediatamente (no tenés que esperar a rebootear).

Los 5 servicios que necesita tu aplicación Python

El artículo de dev.to propone esta arquitectura de 5 servicios independientes, todos corriendo en el mismo VPS:

1. API server (Flask/Gunicorn). El servicio web que escucha requests HTTP. En desarrollo usás el dev server de Flask (flask run), que anda bien para ~500 req/sec en un single core. En producción swapeás por Gunicorn con workers: gunicorn -w 4 -b 0.0.0.0:8083 api:app. Same unit file, solo cambiás la línea de ExecStart.

2. Background collector loop. Proceso que corre cada N segundos o minutos (ejemplo: cada 5 minutos, --loop 300) y hace trabajo async: scraping, sincronización de datos, backups, lo que sea. No bloquea el API server porque corre en otro proceso con su propio interpreter Python.

3. Healthcheck monitor. Servicio que cada X segundos pinea el API (o cualquier otro healthcheck interno) y lo logs / alerta si hay problemas. Detecta caídas antes que los usuarios.

4. Alerts worker. Procesa una queue de alertas (en Redis, una tabla SQL, lo que uses) y envía emails, Slack messages, notificaciones push, etc. No bloquea el API porque está en otro proceso.

5. Dashboard supervisión. Un servicio que expone métricas en un puerto local (/metrics, /health, /status) que un Prometheus local o similar puede scrapear. Útil para monitoreo interno sin agregar observabilidad externa costosa. Sobre eso hablamos en aspectos de seguridad a considerar.

¿Por qué son independientes? Porque cada uno es un archivo .service separado con su propio ExecStart, su propio Restart policy, y su propio log. Si el collector falla, el API sigue arriba. Si el healthcheck detector se congela, el collector y el API siguen funcionando. Systemd maneja los reintentos de forma desacoplada para cada servicio.

Configurar cada .service paso a paso con ejemplos reales

Vamos a los ejemplos concretos de cada archivo, basado en el código del artículo de dev.to:

Servicio 1: API Server Flask (con Gunicorn en producción)

[Unit]
Description=Funding Finder API Server
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/root/project_30d/artifacts/funding_finder
ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/api.py
Restart=always
RestartSec=5
StandardOutput=append:/root/project_30d/artifacts/funding_finder/data/api.log
StandardError=append:/root/project_30d/artifacts/funding_finder/data/api.log
Environment="PYTHONUNBUFFERED=1"

[Install]
WantedBy=multi-user.target

En desarrollo eso corre el dev server de Flask (útil para testing). En producción, reemplazás ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/api.py por ExecStart=/usr/bin/gunicorn -w 4 -b 0.0.0.0:8083 api:app — y listo, mismo archivo .service, diferentes performance.

Servicio 2: Background Data Collector (loop cada 5 minutos)

[Unit]
Description=Funding Finder Data Collector
After=network.target funding-finder-api.service

[Service]
Type=simple
User=deploy
WorkingDirectory=/root/project_30d/artifacts/funding_finder
ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/collector.py --exchanges binance,bybit,okx --loop 300
Restart=always
RestartSec=10
StandardOutput=append:/root/project_30d/artifacts/funding_finder/data/collector.log
StandardError=append:/root/project_30d/artifacts/funding_finder/data/collector.log
Environment="PYTHONUNBUFFERED=1"

[Install]
WantedBy=multi-user.target

Notá el After=funding-finder-api.service — le dice a systemd que espere a que el API arranque antes de arrancar el collector (así el collector puede pinear el API en sus primeras iteraciones sin falla de conexión).

Servicio 3: Healthcheck Monitor

[Unit]
Description=Funding Finder Health Monitor
After=funding-finder-api.service

[Service]
Type=simple
User=deploy
WorkingDirectory=/root/project_30d/artifacts/funding_finder
ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/healthcheck.py --target http://localhost:8083/health --interval 30
Restart=always
RestartSec=10
StandardOutput=append:/root/project_30d/artifacts/funding_finder/data/healthcheck.log
StandardError=append:/root/project_30d/artifacts/funding_finder/data/healthcheck.log

[Install]
WantedBy=multi-user.target

Servicio 4: Alerts Worker (procesa queue de alertas)

[Unit]
Description=Funding Finder Alerts Worker
After=funding-finder-api.service

[Service]
Type=simple
User=deploy
WorkingDirectory=/root/project_30d/artifacts/funding_finder
ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/alerts_worker.py --queue-type redis --redis-url redis://localhost:6379/0
Restart=always
RestartSec=10
StandardOutput=append:/root/project_30d/artifacts/funding_finder/data/alerts.log
StandardError=append:/root/project_30d/artifacts/funding_finder/data/alerts.log

[Install]
WantedBy=multi-user.target

Servicio 5: Dashboard Supervisión (expone métricas locales)

[Unit]
Description=Funding Finder Supervision Dashboard
After=funding-finder-api.service

[Service]
Type=simple
User=deploy
WorkingDirectory=/root/project_30d/artifacts/funding_finder
ExecStart=/usr/bin/python3 /root/project_30d/artifacts/funding_finder/dashboard.py --port 9090
Restart=always
RestartSec=10
StandardOutput=append:/root/project_30d/artifacts/funding_finder/data/dashboard.log
StandardError=append:/root/project_30d/artifacts/funding_finder/data/dashboard.log

[Install]
WantedBy=multi-user.target

Una vez que tés los 5 archivos en /etc/systemd/system/:

sudo systemctl daemon-reload
sudo systemctl enable --now funding-finder-api
sudo systemctl enable --now funding-finder-collector
sudo systemctl enable --now funding-finder-healthcheck
sudo systemctl enable --now funding-finder-alerts
sudo systemctl enable --now funding-finder-dashboard

Systemd se encarga del resto: arranca cada uno respetando el orden de dependencias (After=), reinicia automáticamente si alguno cae, y loguea todo a archivos separados.

Docker vs systemd: cuándo elegir cada uno

Aspectosystemd (nativo Linux)Docker + docker-compose
Consumo de RAM~70 MB total para 5 servicios Python400-800 MB (Docker daemon + containers)
Setup5 archivos .service, 2 comandos systemctlDockerfile, docker-compose.yml, registry, build time
Debuggingjournalctl -u servicio -f, ve los logs en vivodocker logs, docker exec, menos transparencia
EscalabilidadBien para 1-2 VPS, manual para crecerDiseñado para clusters, Kubernetes, auto-scaling
ReproducibilidadDepende del VPS (versiones de libros pueden variar)Exacta: mismo image en prod, staging, local
Overhead operativoBajo — systemd ya está, nada nuevoAlto — Docker daemon, registry, CI/CD, secrets
Mejor paraProyectos personales, MVPs, monolitos pequeños en VPS únicoEquipos grandes, multi-tenancy, teams distribuidos, zero-downtime deploys
systemd python aplicación web diagrama explicativo

Gestionar servicios con systemctl: comandos esenciales y monitoreo

Una vez que los 5 servicios están activos, todo el control pasa por systemctl:

  • sudo systemctl status funding-finder-api — ver el estado actual (arriba, abajo, cuándo fue el último restart).
  • sudo systemctl restart funding-finder-api — reinicia el servicio (útil después de actualizar código).
  • sudo systemctl stop funding-finder-api — lo apaga (no se reinicia automáticamente).
  • sudo systemctl start funding-finder-api — lo arranca de nuevo.
  • sudo systemctl disable funding-finder-api — lo desactiva del arranque en boot (pero sigue corriendo si ya estaba arriba).
  • sudo systemctl disable --now funding-finder-api — lo desactiva Y lo apaga inmediatamente.

Ver logs en vivo:

sudo journalctl -u funding-finder-api -f

El -f es “follow” (like tail -f), así ves los logs mientras suceden. Eso sí, journalctl por defecto solo muestra los últimos ~10 min — si necesitás logs viejos usá: Ya lo cubrimos antes en aplicaciones Python con aceleración GPU.

sudo journalctl -u funding-finder-api --since "2026-04-11 10:00:00"

Monitorear múltiples servicios simultáneamente:

watch -n 1 'systemctl status funding-finder-* | grep -E "(Active|ExecStart)"'

Eso corre cada 1 segundo y te muestra el estado de todos los servicios que matcheen funding-finder-*.

Arquitectura de 5 servicios en un VPS: cómo se coordinan sin Docker

El punto es que los 5 servicios corren COMPLETAMENTE INDEPENDIENTES pero pueden comunicarse por canales estándar:

  • API server expone HTTP en el puerto 8083. El healthcheck lo pinea, el collector le envía datos, el dashboard scrappea métricas.
  • Collector escribe en una DB (SQLite, PostgreSQL, lo que sea). El API server la lee. Si el collector falla y se reinicia, la DB sigue ahí — el API sigue funcional.
  • Alerts worker consume de una queue (Redis, RabbitMQ, o incluso una tabla SQL). Quien quiera enviar una alerta, mete un mensaje en la queue y se olvida. El worker procesa cuando puede.
  • Healthcheck monitor detecta caídas y loguea / dispara webhooks. Es observador pasivo, no interfiere.
  • Dashboard expone estado en HTTP. Cualquiera puede ir a http://localhost:9090/metrics y ver dónde está parada cada cosa.

Systemd garantiza que si cualquiera de los 5 falla, se reinicia automáticamente en 5-10 segundos. Los otros 4 siguen arriba. No hay punto único de falla (a nivel de servicios) — cada uno es su propia cosa.

¿Escalabilidad? Si necesitás agregar un 6º servicio (ej: reportes programados), copias uno de los templates, le cambias el nombre y el ExecStart, y systemctl enable --now. Sin tocar nada más. Sin rebuildear una imagen Docker, sin republish a un registry.

Errores comunes al configurar servicios systemd

1. Olvidar PYTHONUNBUFFERED=1

Python bufferea stdout por defecto. Sin Environment="PYTHONUNBUFFERED=1", los logs de tu script aparecen 30 segundos después de ser escribidos (o nunca). Resultado: ves systemctl status y el servicio se ve muerto cuando en realidad está corriendo. Agregá la variable en [Service] y problema resuelto.

2. WorkingDirectory mal especificado

Si tu script hace open("config.json") sin ruta absoluta, systemd busca el archivo en WorkingDirectory. Si no la pusiste, busca en /root o donde sea que systemd arranque, y falla. Solución: siempre WorkingDirectory=/path/to/project, y en el script usá rutas relativas o absolutas consistently.

3. User=root (o usuario sin permisos)

Correr servicios como root es riesgoso (si el script es comprometido, el atacante es root). Usá un usuario normal con permisos mínimos (ej: User=deploy). Pero asegurate de que ese usuario pueda escribir en el directorio de logs (chown deploy:deploy /ruta/logs). Lo explicamos a fondo en automatizar tu deploy con CI/CD.

4. StandardOutput mal dirigido

Si no configurás StandardOutput/StandardError, los logs se pierden en el void (o aparecen en journalctl pero no en un archivo). Siempre apuntá a un archivo con StandardOutput=append:/path/to/log.log. El append: es importante — significa que append, no trunca.

5. Olvidar daemon-reload después de cambiar un .service

Editas el archivo, guardas, y arrancás systemctl status esperando que los cambios tomen efecto. Pero systemd tiene los archivos en caché. Necesitás sudo systemctl daemon-reload para que systemd relea todos los .service. Luego systemctl restart el servicio afectado. Si no hacés daemon-reload, los cambios nunca aplican.

Preguntas Frecuentes

¿Cómo hago que un servicio dependa de otro?

Usá After=nombre-otro-servicio.service en el [Unit]. Systemd arranca el otro primero. Si querés que FALLE si el otro no arranca, usá además Requires=nombre-otro-servicio.service.

¿Puedo cambiar el código sin detener el servicio?

Depende del tipo de cambio. Si es Python puro (no compiled), editás los .py, y en la próxima iteración del script los cambios aplican (porque Python re-importa). Si cambias dependencias (requirements.txt), tenés que reinstalar en el venv y reiniciar el servicio (systemctl restart). Si cambias la configuración del .service mismo, daemon-reload + restart.

¿Cómo monitorieo múltiples servicios simultáneamente?

watch -n 1 'systemctl status funding-finder-* | grep -E "(Active|ExecStart)"' actualiza cada segundo. O instalás Prometheus/Grafana localmente (complexity overhead). O creás tu propio dashboard en Python que scrappea los logs de cada servicio y los suma en una página.

¿Qué pasa si un servicio crashea 100 veces en 1 minuto?

Systemd tiene un circuit breaker integrado (StartLimitBurst y StartLimitIntervalSec). Por defecto, si un servicio falla 5 veces en 10 segundos, systemd lo da por muerto y deja de reintentar. Cuando debuggees el problema y lo fixes, tendés que hacer systemctl reset-failed nombre.service para resetear el contador, luego systemctl start de nuevo.

¿Puedo pasar argumentos dinámicos al ExecStart?

No directamente en ExecStart. Pero podés: (1) Guardar config en variables de entorno (Environment="KEY=VALUE") y que tu script las lea. (2) Apuntar ExecStart a un script shell que construya el comando dinámicamente. (3) Usar EnvironmentFile=/ruta/config.env para cargar variables desde un archivo.

Conclusión

Systemd no es Docker, ni pretende serlo. Es la forma nativa de Linux de manejar procesos, y para proyectos pequeños en VPS de bajo costo, es más simple, más eficiente y más fácil de debuggear que agregar una capa de orquestación extra. Los 5 servicios del artículo de dev.to (API, collector, healthcheck, alerts, dashboard) consumen 70 MB de RAM total, arrancan automáticamente en boot, se reinician solos si fallan, y loguean centralizadamente en journalctl. Sin Docker. Sin Kubernetes. Sin Watchtower.

Si tu proyecto cabe en un VPS único, systemd es el camino. Una vez que crezcas a múltiples máquinas, múltiples equipos, o necesites reproducibilidad estricta, entonces sí, Docker y Kubernetes tienen sentido. Pero para MVP, prototipo, o hobby project, systemd es oro puro.

Fuentes

Similar Posts