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

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors