Skip to content

ironhack-labs/lab-web-docker-fullstack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

logo_ironhack_blue 7

Lab | App Fullstack Completamente Containerizada

Objetivo

Dockerizar una aplicación fullstack completa — FastAPI + PostgreSQL + React/Vite — de forma que cualquier persona pueda ejecutarla con docker compose up sin instalar Python, Node.js ni PostgreSQL en su máquina.


Setup

# Clonar o usar una app fullstack existente del curso
# Si no tienes una, puedes usar esta estructura de ejemplo:
mkdir lab-web-docker-fullstack
cd lab-web-docker-fullstack
mkdir backend frontend

Estructura objetivo del proyecto:

lab-web-docker-fullstack/
├── docker-compose.yml
├── .env
├── .env.example
├── .dockerignore
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── main.py
│   └── database.py
└── frontend/
    ├── Dockerfile
    ├── nginx.conf
    ├── package.json
    └── src/

Paso 1 — Backend: API FastAPI mínima

# backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from database import engine, Base, SessionLocal
from sqlalchemy import Column, Integer, String, text

# Crear tablas al arrancar (en producción real usarías migraciones)
Base.metadata.create_all(bind=engine)

app = FastAPI(title="Lab Docker API")

# CORS: permitir peticiones desde el frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost", "http://localhost:80"],
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/health")
def health_check():
    """Endpoint que usa el healthcheck del compose para verificar que la API está viva."""
    return {"status": "ok"}

@app.get("/api/items")
def get_items():
    """Devuelve items de ejemplo."""
    return {"items": ["manzana", "pera", "naranja"]}
# backend/database.py
import os
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# Leer la URL de la base de datos desde la variable de entorno
DATABASE_URL = os.environ.get("DATABASE_URL", "postgresql://appuser:apppassword@postgres:5432/appdb")

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# backend/requirements.txt
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9

Paso 2 — Dockerfile del backend

# backend/Dockerfile

# Imagen base ligera
FROM python:3.11-slim

# Evitar archivos .pyc y forzar logs sin buffer
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Crear usuario sin privilegios para ejecutar la app
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser

WORKDIR /app

# Instalar dependencias antes que el código (aprovecha la caché)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar el código y asignar propiedad al usuario sin privilegios
COPY --chown=appuser:appgroup . .

USER appuser

EXPOSE 8000

# --reload solo para desarrollo — en producción quitar el flag
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

Paso 3 — Frontend: app React mínima

<!-- frontend/index.html (en la raíz del proyecto Vite) -->
<!DOCTYPE html>
<html lang="es">
  <head><meta charset="UTF-8" /><title>Lab Docker</title></head>
  <body><div id="root"></div><script type="module" src="/src/main.jsx"></script></body>
</html>
// frontend/src/App.jsx
import { useState, useEffect } from "react"

// La URL de la API se inyecta en tiempo de build desde la variable de entorno
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000"

function App() {
  const [items, setItems] = useState([])
  const [status, setStatus] = useState("cargando...")

  useEffect(() => {
    // Verificar que la API está viva
    fetch(`${API_URL}/health`)
      .then(r => r.json())
      .then(d => setStatus(d.status))
      .catch(() => setStatus("error conectando con la API"))

    // Cargar items
    fetch(`${API_URL}/api/items`)
      .then(r => r.json())
      .then(d => setItems(d.items))
  }, [])

  return (
    <div>
      <h1>Lab Docker Fullstack</h1>
      <p>Estado API: <strong>{status}</strong></p>
      <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>
    </div>
  )
}

export default App

Paso 4 — Dockerfile del frontend

# frontend/Dockerfile

# ── Etapa de build ─────────────────────────────────────────────────────────
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json .
RUN npm ci --silent

COPY . .

# ARG recibe el valor en tiempo de build desde docker-compose
ARG VITE_API_URL=http://localhost:8000
ENV VITE_API_URL=$VITE_API_URL

RUN npm run build

# ── Etapa de producción ────────────────────────────────────────────────────
FROM nginx:alpine AS runner

# Copiar los archivos estáticos construidos
COPY --from=builder /app/dist /usr/share/nginx/html

# Configuración de nginx para que React Router funcione
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
# frontend/nginx.conf
server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Paso 5 — docker-compose.yml completo

# docker-compose.yml

services:

  postgres:
    image: postgres:16-alpine
    container_name: lab-postgres
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: apppassword
      POSTGRES_DB: appdb
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  api:
    build:
      context: ./backend
    container_name: lab-api
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://appuser:apppassword@postgres:5432/appdb
      SECRET_KEY: ${SECRET_KEY:-clave-desarrollo-insegura}
    volumes:
      - ./backend:/app   # hot-reload en desarrollo
    networks:
      - app-network
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  frontend:
    build:
      context: ./frontend
      args:
        VITE_API_URL: http://localhost:8000
    container_name: lab-frontend
    ports:
      - "80:80"
    networks:
      - app-network
    depends_on:
      - api
    restart: unless-stopped

volumes:
  postgres-data:

networks:
  app-network:
    driver: bridge

Paso 6 — Archivos de entorno

# .env (NO subir a git)
SECRET_KEY=mi-clave-secreta-de-desarrollo
# .env.example (SÍ subir a git)
SECRET_KEY=
# .dockerignore
node_modules/
__pycache__/
*.pyc
.venv/
.env
.env.*
!.env.example
dist/
.git/
*.md

Paso 7 — Ejecutar y verificar

# Construir y levantar todo
docker compose up -d --build

# Verificar que todos los servicios están healthy
docker compose ps

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

# Probar la API
curl http://localhost:8000/health
curl http://localhost:8000/api/items

# Abrir el frontend
# http://localhost en el navegador

# Entrar en la base de datos
docker compose exec postgres psql -U appuser -d appdb

# Parar todo al terminar
docker compose down

Requisitos

  • El proyecto arranca completamente con docker compose up -d --build
  • docker compose ps muestra los 3 servicios como "running" o "healthy"
  • El endpoint http://localhost:8000/health responde {"status": "ok"}
  • El frontend es accesible en http://localhost y muestra los items de la API
  • Los datos de PostgreSQL persisten después de docker compose restart
  • El archivo .env no está versionado en git (comprobarlo con git status)

Bonus

  • Añadir un healthcheck al servicio api que verifique /health
  • Hacer que frontend dependa del healthcheck de api
  • Añadir el servicio adminer (puerto 8080) con el profile dev para administrar la BD visualmente
  • Añadir un servicio nginx-proxy que sirva tanto la API como el frontend desde el mismo puerto 80

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors