
Francisco Rhaiel
AI Growth Engineer
Programación y Desarrollo Web
Cómo desplegar tu primera app con Docker y GitHub Actions: guía paso a paso para developers
Publicado el
Dockerizar una aplicación y desplegarla automáticamente con GitHub Actions es el flujo de trabajo estándar en cualquier equipo de desarrollo moderno. Esta guía lo recorre completo, con el código de cada paso, hasta dejar una app funcionando en la nube que se actualiza sola con cada push.
Es uno de los proyectos que más rinde en un portfolio junior, porque demuestra algo que la mayoría de los candidatos no muestra: que entendés cómo el código llega a producción, no solo cómo se escribe.
Qué vas a construir
Una API sencilla en Python con FastAPI, empaquetada en una imagen de Docker, con un flujo de integración y despliegue continuo en GitHub Actions que corre los tests, construye la imagen y despliega automáticamente en cada push a la rama principal.
El mismo esquema aplica casi sin cambios a Node.js, Go o cualquier otro stack.
Requisitos previos
Git y una cuenta de GitHub
Docker instalado localmente
Python 3.11 o superior
Una cuenta gratuita en Railway o Render
Paso 1 — La aplicación
Creá la estructura del proyecto:
app/main.py— la aplicacióntests/test_main.py— las pruebasrequirements.txt— dependenciasDockerfile— la receta de la imagen.github/workflows/deploy.yml— el flujo de CI/CD
En app/main.py:
from fastapi import FastAPI
app = FastAPI(title="Demo API")
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/saludo/{nombre}")
def saludo(nombre: str):
return {"mensaje": f"Hola, {nombre}"}
En requirements.txt:
fastapi==0.115.0
uvicorn[standard]==0.32.0
pytest==8.3.3
httpx==0.27.2
Fijar versiones exactas no es un detalle: es lo que hace que la imagen que construís hoy sea igual a la que construís en tres meses.
En tests/test_main.py:
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health():
r = client.get("/health")
assert r.status_code == 200
assert r.json() == {"status": "ok"}
El endpoint /health parece trivial pero es fundamental: las plataformas de despliegue lo usan para saber si tu app arrancó bien.
Paso 2 — El Dockerfile
Un Dockerfile mal escrito produce imágenes de 1 GB que tardan cinco minutos en construirse. Este usa construcción en múltiples etapas y aprovecha la caché de capas:
FROM python:3.11-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Tres decisiones que importan:
Copiar
requirements.txtantes que el código. Docker cachea por capas: si el código cambia pero las dependencias no, no reinstala nada. Esto reduce el tiempo de construcción de minutos a segundos.Usar
python:3.11-slimen lugar de la imagen completa. Diferencia de cientos de megabytes.No correr como root. Es una de las recomendaciones básicas de la documentación oficial de Docker y de las más ignoradas.
Agregá también un .dockerignore con .git, __pycache__, .venv, tests y .env. Sin él, estás copiando basura y, en el peor caso, credenciales a la imagen.
Probalo localmente
docker build -t demo-api:local .
docker run -p 8000:8000 demo-api:local
Abrí http://localhost:8000/health. Si responde, la imagen está bien. Nunca pases al paso siguiente sin que esto funcione: depurar un Dockerfile desde los logs de CI es mucho más lento.
Paso 3 — El workflow de GitHub Actions
Un workflow es un archivo YAML que le dice a GitHub qué ejecutar y cuándo. En .github/workflows/deploy.yml:
name: CI/CD
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
- run: pip install -r requirements.txt
- run: pytest -q
build-and-deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t demo-api:${{ github.sha }} .
- name: Deploy
env:
DEPLOY_HOOK: ${{ secrets.DEPLOY_HOOK }}
run: curl -fsSL -X POST "$DEPLOY_HOOK"
Qué está pasando acá
ondefine los disparadores: cada push amainy cada pull request.Dos jobs separados.
testcorre siempre;build-and-deploysolo si los tests pasaron (needs: test) y solo en la rama principal. Esa condición es lo que evita desplegar código roto.github.shacomo etiqueta. Cada imagen queda identificada con el commit exacto que la generó, lo que hace trivial saber qué versión está corriendo.secrets.DEPLOY_HOOK. La URL de despliegue nunca va en el código. Se carga en Settings → Secrets and variables → Actions del repositorio.
La documentación oficial de GitHub Actions cubre el resto de la sintaxis: matrices de versiones, entornos con aprobación manual y reutilización de workflows.
Paso 4 — Desplegar en Railway o Render
Railway
Creá un proyecto y conectá tu repositorio de GitHub.
Railway detecta el Dockerfile automáticamente y lo usa para construir.
Configurá el puerto en las variables de entorno y ajustá el
CMDpara leer$PORTsi la plataforma lo asigna dinámicamente.Copiá la URL del deploy hook y guardala como secreto en GitHub.
Render
Creá un Web Service apuntando al repositorio, con entorno "Docker".
Definí el health check path en
/health.Render genera una Deploy Hook URL en la configuración del servicio; ese es el valor del secreto.
Ambas tienen plan gratuito suficiente para un proyecto de portfolio. La contrapartida es que suelen suspender el servicio tras un período de inactividad, así que la primera petición después de un rato puede tardar unos segundos.
Paso 5 — Verificar que funciona
Hacé un cambio mínimo, por ejemplo modificá el mensaje del endpoint /saludo, y subilo:
git add . && git commit -m "cambio de prueba" && git push origin main
En la pestaña Actions del repositorio deberías ver el workflow corriendo: primero los tests, después la construcción y el despliegue. En uno o dos minutos, la URL pública debería devolver el nuevo mensaje.
Si algo falla, el orden de revisión es: logs del job que falló → reproducir el mismo comando localmente → verificar que los secretos estén cargados con el nombre exacto.
Errores frecuentes y cómo resolverlos
Síntoma | Causa habitual | Solución |
|---|---|---|
La imagen pesa más de 1 GB | Imagen base completa, falta .dockerignore | Usar |
El build tarda varios minutos siempre | Se copia el código antes de las dependencias | Copiar |
La app no responde en la nube | Escucha en 127.0.0.1 en vez de 0.0.0.0 | Usar |
El deploy corre aunque fallen los tests | Falta | Agregar la dependencia entre jobs |
Credenciales expuestas en logs | Variables impresas o hardcodeadas | Usar secrets y nunca hacer echo de ellos |
Cómo presentar esto en tu portfolio
El repositorio vale más si el README explica las decisiones, no solo los comandos. Incluí:
Un diagrama simple del flujo: push → tests → build → deploy
Por qué elegiste construcción en capas y qué tiempo de build ahorraste
Qué pasa cuando un test falla (con captura del workflow en rojo)
La URL pública funcionando
Ese contexto es lo que convierte un repositorio más en un caso que podés defender en una entrevista. Si querés profundizar en el ecosistema de contenedores más allá de este proyecto, este artículo sobre qué son Docker y Kubernetes y cómo aprenderlos siendo desarrollador junior ordena el camino siguiente.
Cursos recomendados de Coderhouse
Para consolidar lo que vimos acá y avanzar hacia un perfil de infraestructura:
Curso de DevOps & Cloud — el recorrido directo de este artículo: contenedores, CI/CD, infraestructura como código y monitoreo.
Curso de Cloud Computing AWS — el siguiente paso natural cuando querés salir de plataformas gestionadas y manejar la infraestructura vos.
Curso de Python — si el stack del ejemplo te resultó nuevo, acá está la base del lenguaje.
Carrera de Desarrollo Backend — el recorrido completo de APIs, bases de datos y arquitectura, con despliegue incluido.
Preguntas frecuentes
¿Necesito saber Docker para conseguir trabajo como desarrollador junior?
No es requisito excluyente en la mayoría de las búsquedas junior, pero aparece cada vez más y es uno de los diferenciales más baratos de conseguir. Entender cómo empaquetar una aplicación y por qué eso resuelve el problema de "en mi máquina funciona" te separa de la mayoría de los candidatos de entrada.
¿Cuál es la diferencia entre integración continua y despliegue continuo?
La integración continua (CI) es la parte que ejecuta automáticamente los tests y las verificaciones cada vez que alguien sube código: su objetivo es detectar problemas temprano. El despliegue continuo (CD) es la parte que lleva ese código a producción sin intervención manual. El workflow de esta guía hace las dos cosas: el job test es CI y el job build-and-deploy es CD.
¿Railway o Render: cuál conviene para un proyecto de portfolio?
Ambas funcionan bien y tienen plan gratuito. Railway suele ser más rápida de configurar y tiene mejor experiencia con bases de datos asociadas. Render tiene health checks más explícitos y documentación más clara sobre Docker. Para un proyecto de portfolio, elegí la que configures más rápido: lo que se evalúa es el flujo, no la plataforma.
¿Qué pasa si mi aplicación necesita una base de datos?
El esquema no cambia demasiado: agregás la base como servicio en la plataforma de despliegue, pasás la cadena de conexión como variable de entorno (nunca en el código), y en el workflow de CI usás un servicio de base de datos temporal para correr los tests. GitHub Actions permite levantar contenedores auxiliares dentro del job para exactamente eso.
¿Conviene publicar la imagen en un registry?
Para un proyecto de portfolio no es imprescindible, pero suma. GitHub Container Registry está integrado con Actions y se configura en pocas líneas. La ventaja real es de trazabilidad: podés desplegar exactamente la misma imagen que testeaste, en lugar de reconstruirla en el servidor de destino, que es la práctica correcta en entornos de producción.

Sobre el autor
Soy Francisco Rhaiel, AI Growth Engineer en Coderhouse. Mi día a día consiste en automatizar y optimizar procesos aplicando lo último en inteligencia artificial, incluyendo Agentic AI y Gen AI. Soy graduado de la Universidad Torcuato Di Tella (UTDT) en Tecnología Digital, y mi recorrido me llevó a especializarme en la intersección entre tecnología, datos y negocio. Me mueve aprender, construir y aplicar tecnologías innovadoras para resolver problemas reales. Para profundizar en mi trayectoria, te espero en mi perfil de LinkedIn.
Artículos destacados
Ver todos los artículos
Guía de Rol
Business Intelligence como carrera: qué hace un BI Analyst, cuánto gana y cómo entrar al sector
Publicado el
Tutorial: Guía Paso a Paso
APIs de IA para principiantes: cómo conectar GPT, Claude y Gemini a tus proyectos
Publicado el
Comparativa de Herramientas
Business Intelligence vs Data Analytics: diferencias clave, herramientas y qué aprender primero en Argentina
Publicado el
Uso de IA
Agentes de IA: qué son, para qué sirven y cómo empezar a usarlos en tu trabajo
Publicado el
Tutorial: Guía Paso a Paso
Claude Cowork vs. ChatGPT: ¿Cuál es la mejor herramienta?
Publicado el
Ruta de Aprendizaje
¿Cómo aprender inteligencia artificial desde cero? Guía Completa
Publicado el

