Problema

Los usuarios que quieren auto‑alojar una aplicación web con funcionalidades modernas (API REST, escaneo de códigos de barra, autenticación OIDC, integración AI) suelen tropezar con tres patrones recurrentes:

  1. Persistencia de datos: la base de datos se pierde al reiniciar el contenedor o al actualizar la imagen.
  2. Configuración de credenciales externas: APIs de terceros (OpenFoodFacts, Ollama) requieren claves que deben mantenerse seguras y accesibles para el contenedor.
  3. Escalado y orquestación: pasar de un entorno de desarrollo con Docker Compose a producción con Kubernetes genera incompatibilidades en volúmenes, variables de entorno y políticas de red.

Estos fallos aparecen tanto en entornos de homelab como en despliegues de pequeña empresa, y el síntoma típico es que la aplicación arranca pero no puede leer datos, se desconecta de servicios externos o se reinicia indefinidamente.

Causa

1. Volúmenes efímeros

Docker crea volúmenes anónimos si no se declara explícitamente -v. En Kubernetes, los emptyDir se destruyen al pod morir, mientras que los hostPath pueden no existir en todos los nodos.

2. Variables de entorno expuestas

Colocar claves API directamente en docker-compose.yml o en values.yaml sin usar secretos lleva a que queden en el historial de Git o en logs del clúster.

3. Diferencias en la red interna

Docker Compose usa una red bridge por defecto; Kubernetes usa ClusterIP y necesita Service y Ingress para exponer puertos. Los contenedores que esperan localhost para comunicarse con la base de datos fallan cuando se despliegan en pods separados.

4. Falta de health checks

Sin HEALTHCHECK en Docker o livenessProbe en Kubernetes, el orquestador no detecta que la aplicación está en estado de error y la reinicia sin parar el problema subyacente.

Solución

Adoptar un enfoque modular que separe persistencia, configuración segura y exposición de red. La solución funciona tanto con Docker Compose como con Helm, permitiendo una migración fluida.

Paso 1: Definir volúmenes nombrados

En Docker Compose:

services:
  app:
    image: ghcr.io/usuario/app:latest
    volumes:
      - app-data:/var/lib/app
volumes:
  app-data:

En Helm (values.yaml):

persistence:
  enabled: true
  size: 5Gi
  storageClass: standard

Esto garantiza que la base de datos y los archivos de usuario sobrevivan a reinicios y actualizaciones.

Paso 2: Utilizar secretos para credenciales

Docker Compose con archivo .env que no se versiona:

APP_OPENFOODFACTS_KEY=xxxxxxxx
APP_OLLAMA_API_KEY=yyyyyyyy

En Kubernetes, crear un Secret y referenciarlo:

kubectl create secret generic app-secrets \
  --from-literal=OPENFOODFACTS_KEY=xxxxxxxx \
  --from-literal=OLLAMA_API_KEY=yyyyyyyy

Y en el chart:

envFrom:
  - secretRef:
      name: app-secrets

Paso 3: Unificar la red con nombres DNS internos

En Docker Compose, declarar una red explícita:

networks:
  appnet:
services:
  app:
    networks:
      - appnet
  db:
    networks:
      - appnet

En Kubernetes, usar el nombre del Service como host:

env:
  - name: DATABASE_HOST
    value: app-db

El contenedor app podrá resolver app-db sin depender de localhost.

Paso 4: Añadir health checks

Docker:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 5s
  retries: 3

Kubernetes:

livenessProbe:
  httpGet:
    path: /health
    port: http
  initialDelaySeconds: 15
  periodSeconds: 30
readinessProbe:
  httpGet:
    path: /ready
    port: http
  initialDelaySeconds: 5
  periodSeconds: 10

Los probes evitan reinicios innecesarios y permiten que el orquestador retire pods que no responden.

Paso 5: Opcional – Integración AI local

Si la aplicación usa Ollama para estimar calorías a partir de fotos, montar el modelo como un contenedor separado y comunicarlo vía http://ollama:11434. En Helm, habilitar un sub‑chart:

ollama:
  enabled: true
  image: ollama/ollama:latest
  resources:
    limits:
      cpu: "1"
      memory: "2Gi"

Esto mantiene la IA dentro del clúster y elimina la dependencia de claves externas.

Cuándo aplicar esta solución

  • Entornos de homelab donde se desea migrar de Docker Compose a Kubernetes sin rehacer la configuración.
  • Aplicaciones con datos críticos (tracking de métricas, historial de peso) que no pueden perderse entre despliegues.
  • Servicios que consumen APIs externas y requieren manejo seguro de credenciales.
  • Escenarios donde se planea escalar horizontalmente; los volúmenes y la red deben ser compatibles con múltiples réplicas.

No es necesario aplicar todo el stack si la aplicación se ejecuta en un único contenedor sin base de datos persistente ni integración AI. En esos casos, basta con un docker-compose.yml simple y variables de entorno locales.

Código

# docker-compose.yml básico
version: "3.8"
services:
  app:
    image: ghcr.io/usuario/app:latest
    ports:
      - "8080:8080"
    env_file: .env
    volumes:
      - app-data:/var/lib/app
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    networks:
      - appnet
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - appnet
volumes:
  app-data:
  db-data:
networks:
  appnet:
# Helm install (asumiendo chart llamado selfhosted-app)
helm repo add myrepo https://example.com/charts
helm install myapp myrepo/selfhosted-app \
  --set persistence.enabled=true \
  --set secret.create=true \
  --set secret.openFoodFactsKey=xxxxxxxx \
  --set secret.ollamaApiKey=yyyyyyyy

Verificación

  1. Persistencia: Detén y elimina los contenedores/pods. Vuelve a levantar y verifica que los datos de usuarios siguen presentes (SELECT * FROM users; en la DB).
  2. Credenciales: Revisa que la aplicación pueda consultar OpenFoodFacts sin errores 401. En logs debe aparecer Authenticated with OpenFoodFacts.
  3. Health checks: Ejecuta docker ps o kubectl get pods y confirma que el estado sea healthy/Ready.
  4. Escalado: Incrementa la réplica a 3 (docker-compose up --scale app=3 o kubectl scale deployment myapp --replicas=3) y verifica que todas respondan al endpoint /health.

Notas adicionales

  • Cuando uses hostPath en Kubernetes, asegúrate de que el nodo tenga suficiente espacio y que el path exista; de lo contrario el pod fallará al iniciar.
  • Mantén el archivo .env fuera del repositorio y usa .gitignore. En entornos CI/CD, inyecta variables mediante secret managers (GitHub Secrets, GitLab CI variables).
  • Si la aplicación necesita acceso a la cámara del móvil a través de la API AI, considera exponer el contenedor Ollama solo dentro del clúster y usar un Ingress con TLS para evitar exposición pública.
  • Las pruebas de extremo a extremo (E2E) pueden automatizarse con Cypress o Playwright dentro de un contenedor CI; esto ayuda a detectar roturas después de actualizar la base de datos o los modelos AI.