Dockerización de una app fullstack
Contenedores para frontend, backend y base de datos: docker-compose multi-stage para producción.
Ya dockerizaste una API simple. Ahora vamos al siguiente nivel: una app fullstack con frontend React, backend Hono, base de datos PostgreSQL y todo orquestado con Docker Compose.
En esta guía vas a crear Dockerfiles de producción para cada servicio y un docker-compose que los levante todos.
Estructura del proyecto
mi-app-fullstack/
├── frontend/ # React + Vite
│ ├── Dockerfile
│ ├── src/
│ └── package.json
├── backend/ # Hono + Prisma
│ ├── Dockerfile
│ ├── src/
│ └── package.json
├── docker-compose.yml
└── .env
Frontend Dockerfile (multi-stage)
# frontend/Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# frontend/nginx.conf
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend:3000/;
proxy_set_header Host $host;
}
}
Nginx sirve los archivos estáticos y hace proxy de /api/ al backend.
Backend Dockerfile
# backend/Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm exec prisma generate
RUN pnpm build
FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Docker Compose
# docker-compose.yml
services:
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
restart: unless-stopped
backend:
build: ./backend
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:secreta@db:5432/app
JWT_SECRET: ${JWT_SECRET}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD: secreta
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
Variables de entorno en producción
# .env (no commitear)
JWT_SECRET=super-secreto-generado-con-openssl-rand-hex-32
DATABASE_URL=postgres://postgres:secreta@db:5432/app
# Generar JWT_SECRET seguro
openssl rand -hex 32
Por qué importa
Dockerizar una app fullstack te da consistencia total: mismo entorno en desarrollo, staging y producción.
La IA y Docker fullstack
Lo bueno
- Generar Dockerfiles: la IA crea multi-stage builds para cada servicio.
- Crear docker-compose: la IA orquesta múltiples servicios con healthchecks.
Lo que no debes hacer
- No commitees .env con secretos. Usa variables de entorno del sistema o un secrets manager.
- No expongas puertos de BD en producción. El
ports: "5432:5432"es solo para desarrollo.
Desafío: dockeriza tu app fullstack
Objetivo: crear Dockerfiles y docker-compose para tu app.
Tu tarea:
- Crea Dockerfile multi-stage para el frontend con Nginx
- Crea Dockerfile multi-stage para el backend
- Crea docker-compose con frontend, backend y PostgreSQL
- Verifica que todo funcione con
docker compose up
Bonus: agrega Redis para caching y un servicio de migraciones.
Para seguir explorando
Resumen
- Multi-stage builds producen imágenes de producción pequeñas.
- Nginx sirve archivos estáticos y hace proxy al backend.
- Docker Compose orquesta múltiples servicios con dependencias y healthchecks.
- Variables de entorno se gestionan con
.env(no commitear). - Healthchecks aseguran que los servicios dependientes estén listos antes de iniciar.
En la próxima guía: Testing: unitario, integración y E2E — cómo asegurar que tu código funciona.