Despliegue con Docker y componentes personalizados en Streamlit

Avanzado
Streamlit
Streamlit
Actualizado: 26/04/2026

Dockerfile para Streamlit

Un Dockerfile bien construido para Streamlit debe:

  • Usar una imagen base ligera de Python
  • Copiar solo los archivos necesarios
  • Instalar las dependencias desde requirements.txt
  • Exponer el puerto 8501 (puerto por defecto de Streamlit)
  • Configurar la app para ejecutarse en modo headless
# Dockerfile
FROM python:3.12-slim

# Crear usuario no root para mayor seguridad
RUN useradd -m -u 1000 appuser

# Directorio de trabajo
WORKDIR /app

# Copiar dependencias primero (aprovecha la caché de capas de Docker)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar el código de la aplicación
COPY . .

# Cambiar al usuario no root
USER appuser

# Exponer el puerto de Streamlit
EXPOSE 8501

# Variables de entorno para producción
ENV STREAMLIT_SERVER_HEADLESS=true \
    STREAMLIT_SERVER_PORT=8501 \
    STREAMLIT_BROWSER_GATHER_USAGE_STATS=false

# Comando de inicio
CMD ["streamlit", "run", "app.py", "--server.address=0.0.0.0"]
flowchart TD
    A[Código app.py y requirements.txt] --> B[Dockerfile python 3.12 slim]
    B --> C["COPY requirements + RUN pip install"]
    B --> D[COPY código proyecto]
    B --> E[EXPOSE 8501]
    B --> F[ENTRYPOINT streamlit run]
    F --> G[docker build -t mi-app]
    G --> H[docker run -p 8501 mi-app]
    G --> I[docker compose con postgres redis]
    I --> J[Servicios orquestados]
    H --> K[App corriendo aislada]
    J --> K
    K --> L[Deploy a servidor o cloud]
    L --> M[Render Heroku ECS Cloud Run]

.dockerignore

Evita incluir archivos innecesarios en la imagen Docker:

# .dockerignore
.git
.gitignore
.venv
__pycache__
*.pyc
*.pyo
.pytest_cache
*.egg-info
.streamlit/secrets.toml    # ¡NUNCA incluir secretos en la imagen!
node_modules
*.md
tests/

Construir y ejecutar la imagen

# Construir la imagen con una etiqueta de versión
docker build -t mi-app-streamlit:1.0 .

# Ejecutar el contenedor
docker run -p 8501:8501 mi-app-streamlit:1.0

# Ejecutar en segundo plano con reinicio automático
docker run -d \
  --name mi-app \
  --restart unless-stopped \
  -p 8501:8501 \
  mi-app-streamlit:1.0

# Pasar secretos como variables de entorno (en lugar de secrets.toml)
docker run -d \
  --name mi-app \
  -p 8501:8501 \
  -e DATABASE_URL="postgresql://user:pass@host/db" \
  -e OPENAI_API_KEY="sk-..." \
  mi-app-streamlit:1.0

Accede a la aplicación en: http://localhost:8501

Acceder a secretos de entorno en Streamlit

import streamlit as st
import os

# Leer desde variables de entorno (alternativa a secrets.toml en Docker)
database_url = os.environ.get("DATABASE_URL") or st.secrets.get("database_url", "")
api_key = os.environ.get("OPENAI_API_KEY") or st.secrets.get("openai_api_key", "")

if not database_url:
    st.error("Variable DATABASE_URL no configurada.")
    st.stop()

Docker Compose: Streamlit + PostgreSQL

Para aplicaciones que requieren una base de datos, docker-compose.yml orquesta ambos servicios:

# docker-compose.yml
version: "3.9"

services:
  app:
    build: .
    ports:
      - "8501:8501"
    environment:
      - DATABASE_URL=postgresql://appuser:secreto@db:5432/miapp
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./assets:/app/assets:ro    # Montar assets como solo lectura
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: miapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: secreto
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d miapp"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
# Levantar todos los servicios
docker compose up -d

# Ver logs en tiempo real
docker compose logs -f app

# Parar y eliminar los contenedores
docker compose down

# Reconstruir la imagen tras cambios
docker compose up -d --build

Dockerfile multietapa para producción

Para reducir el tamaño de la imagen final:

# Etapa 1: instalar dependencias
FROM python:3.12-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

# Etapa 2: imagen final mínima
FROM python:3.12-slim

WORKDIR /app

# Copiar solo las dependencias instaladas, no el pip entero
COPY --from=builder /root/.local /root/.local
COPY . .

ENV PATH=/root/.local/bin:$PATH
ENV STREAMLIT_SERVER_HEADLESS=true \
    STREAMLIT_SERVER_PORT=8501

EXPOSE 8501
CMD ["streamlit", "run", "app.py", "--server.address=0.0.0.0"]

Componentes personalizados de la comunidad

Streamlit permite instalar componentes creados por la comunidad para añadir funcionalidades no incluidas en el core. Se instalan con pip:

streamlit-aggrid: tabla avanzada con filtros y edición

pip install streamlit-aggrid
import streamlit as st
import pandas as pd
from st_aggrid import AgGrid, GridOptionsBuilder

df = pd.DataFrame({
    "nombre": ["Ana García", "Carlos López", "María Martínez"],
    "departamento": ["Ingeniería", "Marketing", "Ventas"],
    "salario": [55000, 42000, 38000],
    "activo": [True, True, False]
})

gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_pagination(paginationAutoPageSize=True)
gb.configure_default_column(editable=True, filter=True, sortable=True)
gb.configure_column("salario", type=["numericColumn", "numberColumnFilter"])
grid_options = gb.build()

resultado = AgGrid(
    df,
    gridOptions=grid_options,
    height=300,
    fit_columns_on_grid_load=True
)

st.write("Datos modificados:", resultado["data"])

streamlit-folium: mapas interactivos con Folium

pip install streamlit-folium folium
import streamlit as st
import folium
from streamlit_folium import st_folium

# Crear mapa centrado en Madrid
mapa = folium.Map(location=[40.4168, -3.7038], zoom_start=12)

# Añadir marcador
folium.Marker(
    [40.4168, -3.7038],
    popup="<b>Madrid</b><br>Capital de España",
    tooltip="Haz clic para más info"
).add_to(mapa)

# Mostrar en Streamlit
resultado = st_folium(mapa, width=700, height=450)

if resultado["last_object_clicked"]:
    st.write("Clic en:", resultado["last_object_clicked"])

streamlit-lottie: animaciones Lottie

pip install streamlit-lottie requests
import streamlit as st
import requests
from streamlit_lottie import st_lottie

def cargar_lottie(url: str):
    r = requests.get(url)
    return r.json() if r.status_code == 200 else None

animacion = cargar_lottie("https://assets5.lottiefiles.com/packages/lf20_jcikwtux.json")

col1, col2 = st.columns([1, 2])
with col1:
    if animacion:
        st_lottie(animacion, height=200, key="carga")
with col2:
    st.title("Cargando datos...")
    st.caption("Animación Lottie integrada en Streamlit")

streamlit-option-menu: menú de navegación personalizado

pip install streamlit-option-menu
import streamlit as st
from streamlit_option_menu import option_menu

with st.sidebar:
    seleccion = option_menu(
        menu_title="Menú principal",
        options=["Inicio", "Dashboard", "Análisis", "Configuración"],
        icons=["house", "bar-chart", "search", "gear"],
        menu_icon="cast",
        default_index=0
    )

if seleccion == "Inicio":
    st.title("🏠 Inicio")
elif seleccion == "Dashboard":
    st.title("📊 Dashboard")
elif seleccion == "Análisis":
    st.title("🔍 Análisis")
elif seleccion == "Configuración":
    st.title("⚙️ Configuración")

Arquitectura de un componente personalizado

Los componentes personalizados tienen dos partes:

  1. Frontend: código HTML/CSS/JavaScript (React opcional) que renderiza el componente en el navegador
  2. Backend Python: clase o función que hace el puente entre el frontend y el script de Streamlit
# Ejemplo mínimo de cómo está construido un componente internamente
import streamlit.components.v1 as components

# Declarar el componente (en el paquete del componente)
mi_componente = components.declare_component(
    "mi_componente",
    url="http://localhost:3001"  # URL del servidor de desarrollo del frontend
)

# Uso en la aplicación
valor = mi_componente(texto="Hola", key="comp1")
st.write(f"Valor devuelto por el componente: {valor}")

Para usar un componente HTML estático sin framework:

import streamlit.components.v1 as components

# Incrustar HTML arbitrario
components.html(
    """
    <div style="background: linear-gradient(135deg, #FF4B4B, #FF8C00);
                padding: 20px; border-radius: 10px; color: white; text-align: center;">
        <h2>Componente HTML personalizado</h2>
        <p>Puede contener cualquier HTML, CSS y JavaScript.</p>
    </div>
    """,
    height=150
)

# Incrustar un iframe
components.iframe("https://docs.streamlit.io", height=400, scrolling=True)

Despliegue en la nube con la imagen Docker

Una vez construida la imagen, puedes desplegarla en cualquier proveedor cloud:

# Google Cloud Run
gcloud run deploy mi-app-streamlit \
  --image gcr.io/mi-proyecto/mi-app-streamlit:1.0 \
  --platform managed \
  --region europe-west1 \
  --allow-unauthenticated \
  --port 8501

# AWS App Runner (desde ECR)
aws ecr get-login-password | docker login --username AWS --password-stdin 123456789.dkr.ecr.eu-west-1.amazonaws.com
docker tag mi-app-streamlit:1.0 123456789.dkr.ecr.eu-west-1.amazonaws.com/mi-app:latest
docker push 123456789.dkr.ecr.eu-west-1.amazonaws.com/mi-app:latest

# Azure Container Apps
az containerapp create \
  --name mi-app-streamlit \
  --image miregistro.azurecr.io/mi-app:1.0 \
  --target-port 8501 \
  --ingress external
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Streamlit es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Streamlit

Explora más contenido relacionado con Streamlit y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Escribir un Dockerfile optimizado para una aplicación Streamlit de producción. Crear una imagen Docker y ejecutar la aplicación con docker run. Usar Docker Compose para orquestar Streamlit junto a PostgreSQL u otros servicios. Instalar y usar componentes personalizados populares de la comunidad Streamlit. Entender la arquitectura de un componente personalizado (frontend. Python backend).