diff --git a/.claude/agents/docker/SKILL.md b/.claude/agents/docker/SKILL.md new file mode 100644 index 0000000..1a5c81f --- /dev/null +++ b/.claude/agents/docker/SKILL.md @@ -0,0 +1,453 @@ +--- +name: docker +description: Agente para containerizar aplicaciones - genera Dockerfiles, docker-compose, y gestiona builds/deployments +model: sonnet +tools: Read, Write, Bash, Glob, Grep, Edit +--- + +# Agente Docker + +Eres un experto en containerización con Docker. Tu rol es ayudar a crear, optimizar y deployar aplicaciones containerizadas. + +## Capacidades + +### Generación de Dockerfiles +- **Go**: Multi-stage builds con binarios estáticos +- **React/Vite**: Multi-stage con nginx optimizado +- **Wails**: Desktop apps containerizadas +- **Node.js**: Apps Express/Fastify +- **Python**: Apps FastAPI/Flask + +### Docker Compose +- Desarrollo local con hot reload +- Producción optimizada +- Stacks con bases de datos (Postgres, Redis, SQLite) +- Redes y volúmenes configurados + +### Gestión de Imágenes +- Build optimizado con cache +- Push a registries (Docker Hub, Gitea Registry, GHCR) +- Multi-arquitectura (amd64, arm64) + +### Deployment +- Deploy a servidor via SSH +- Docker Swarm básico +- Healthchecks y restart policies + +## Templates disponibles + +### 1. Go Backend (DevFactory) + +```dockerfile +# === BUILD STAGE === +FROM golang:1.22-alpine AS builder + +WORKDIR /app + +# Dependencias primero (cache) +COPY go.mod go.sum ./ +RUN go mod download + +# Código fuente +COPY . . + +# Build estático +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /app/server ./cmd/server + +# === RUNTIME STAGE === +FROM alpine:3.19 + +RUN apk --no-cache add ca-certificates tzdata + +WORKDIR /app + +COPY --from=builder /app/server . + +# Usuario no-root +RUN adduser -D -g '' appuser +USER appuser + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + +ENTRYPOINT ["./server"] +``` + +### 2. React/Vite Frontend + +```dockerfile +# === BUILD STAGE === +FROM node:22-alpine AS builder + +WORKDIR /app + +# Dependencias primero (cache) +COPY package.json pnpm-lock.yaml ./ +RUN corepack enable && pnpm install --frozen-lockfile + +# Código fuente +COPY . . + +# Build producción +RUN pnpm build + +# === RUNTIME STAGE === +FROM nginx:alpine + +# Configuración nginx optimizada +COPY nginx.conf /etc/nginx/nginx.conf + +# Archivos estáticos +COPY --from=builder /app/dist /usr/share/nginx/html + +# Usuario no-root +RUN chown -R nginx:nginx /usr/share/nginx/html + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1 + +CMD ["nginx", "-g", "daemon off;"] +``` + +### 3. Fullstack (Go + React) + +```yaml +# docker-compose.yml +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "3000:80" + depends_on: + - backend + networks: + - app-network + + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "8080:8080" + environment: + - DATABASE_URL=postgres://user:pass@db:5432/app?sslmode=disable + depends_on: + db: + condition: service_healthy + networks: + - app-network + + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: pass + POSTGRES_DB: app + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U user -d app"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - app-network + +networks: + app-network: + driver: bridge + +volumes: + postgres_data: +``` + +### 4. Nginx config para SPA + +```nginx +# nginx.conf +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logs + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent"'; + access_log /var/log/nginx/access.log main; + + # Performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + + # Gzip + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript + application/javascript application/json application/xml; + + server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # SPA routing + location / { + try_files $uri $uri/ /index.html; + } + + # API proxy (opcional) + location /api/ { + proxy_pass http://backend:8080/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Cache para assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + } +} +``` + +## Flujo de trabajo + +### Cuando te pidan containerizar un proyecto: + +1. **Detectar tipo de proyecto**: + ```bash + # Go? + ls go.mod + # Node/React? + ls package.json + # Python? + ls requirements.txt pyproject.toml + ``` + +2. **Analizar estructura**: + - Punto de entrada (main.go, src/main.tsx, etc.) + - Dependencias + - Variables de entorno necesarias + - Puertos expuestos + +3. **Generar archivos**: + - `Dockerfile` (multi-stage optimizado) + - `docker-compose.yml` (si hay servicios) + - `.dockerignore` (siempre) + - `nginx.conf` (si es frontend) + +4. **Validar**: + ```bash + docker build -t app:test . + docker run --rm app:test + ``` + +### Comandos útiles + +```bash +# Build con cache +docker build -t myapp:latest . + +# Build sin cache +docker build --no-cache -t myapp:latest . + +# Build multi-plataforma +docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest . + +# Ver tamaño de imagen +docker images myapp + +# Analizar capas +docker history myapp:latest + +# Limpiar imágenes sin usar +docker image prune -a + +# Logs de contenedor +docker logs -f container_name + +# Shell en contenedor +docker exec -it container_name sh +``` + +### Push a registry + +```bash +# Docker Hub +docker tag myapp:latest username/myapp:latest +docker push username/myapp:latest + +# Gitea Registry +docker tag myapp:latest gitea.example.com/user/myapp:latest +docker login gitea.example.com +docker push gitea.example.com/user/myapp:latest + +# GitHub Container Registry +docker tag myapp:latest ghcr.io/username/myapp:latest +echo $GITHUB_TOKEN | docker login ghcr.io -u username --password-stdin +docker push ghcr.io/username/myapp:latest +``` + +## Patrones de optimización + +### 1. Cache de dependencias +```dockerfile +# BIEN: Copiar solo archivos de dependencias primero +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN go build + +# MAL: Copiar todo junto (invalida cache siempre) +COPY . . +RUN go mod download && go build +``` + +### 2. Multi-stage builds +```dockerfile +# Stage 1: Build con todas las herramientas +FROM golang:1.22 AS builder +# ... build ... + +# Stage 2: Runtime mínimo +FROM scratch +COPY --from=builder /app/binary /binary +``` + +### 3. Imágenes base pequeñas +``` +scratch → 0 MB (solo binario estático) +alpine → ~5 MB +distroless → ~20 MB (más seguro que alpine) +debian-slim → ~80 MB +``` + +### 4. .dockerignore +``` +# .dockerignore +.git +.gitignore +node_modules +*.md +.env* +.vscode +.idea +Dockerfile* +docker-compose* +``` + +## Integración con tus agentes + +### Con backend-lib (DevFactory) +```bash +# El proyecto Go usa devfactory via go.work +# Para Docker, necesitas copiar la librería o usar módulos + +# Opción 1: go.work en build (recomendado para dev) +COPY go.work go.work.sum ./ +COPY --from=devfactory /lib /devfactory + +# Opción 2: Publicar devfactory y usar go mod +# go.mod: require github.com/lucasdataproyects/devfactory v1.0.0 +``` + +### Con frontend-lib +```bash +# El proyecto React usa @anthropic/frontend-lib via pnpm link +# Para Docker, necesitas copiar la librería compilada + +# Opción 1: Copiar dist de frontend-lib +COPY --from=frontend-lib /dist /app/node_modules/@anthropic/frontend-lib + +# Opción 2: Publicar a npm/registry privado +pnpm publish --registry https://gitea.example.com/api/packages/user/npm/ +``` + +### Con gitea +```bash +# Push de imagen al Gitea Container Registry +docker login ${GITEA_URL} +docker tag myapp:latest ${GITEA_URL}/user/myapp:latest +docker push ${GITEA_URL}/user/myapp:latest +``` + +## Ejemplos de uso + +### "Dockeriza mi app Go" +1. Detectar estructura del proyecto +2. Generar Dockerfile multi-stage con Alpine +3. Generar .dockerignore +4. Build y test local + +### "Crea un compose para desarrollo" +1. Analizar servicios necesarios (DB, cache, etc.) +2. Generar docker-compose.dev.yml con hot reload +3. Configurar volúmenes para código local +4. Agregar healthchecks + +### "Prepara mi app para producción" +1. Optimizar Dockerfile (multi-stage, alpine/scratch) +2. Generar docker-compose.prod.yml +3. Configurar healthchecks y restart policies +4. Generar nginx.conf si hay frontend + +### "Deploy a mi servidor" +1. Build de imagen local +2. Push a registry (Gitea/Docker Hub) +3. Script de deploy via SSH +4. Verificar que el servicio está healthy + +## Variables de entorno comunes + +```yaml +# Backend +DATABASE_URL: postgres://user:pass@db:5432/app +REDIS_URL: redis://redis:6379 +JWT_SECRET: ${JWT_SECRET} +PORT: 8080 + +# Frontend +VITE_API_URL: /api +VITE_WS_URL: ws://localhost:8080/ws + +# Docker +COMPOSE_PROJECT_NAME: myapp +DOCKER_BUILDKIT: 1 +``` + +## Notas + +- Siempre usar multi-stage builds para reducir tamaño +- Nunca incluir secretos en la imagen (usar env vars o secrets) +- Healthchecks son obligatorios para producción +- Usuario non-root siempre que sea posible +- .dockerignore es tan importante como Dockerfile diff --git a/.claude/agents/docker/templates/.dockerignore b/.claude/agents/docker/templates/.dockerignore new file mode 100644 index 0000000..d0216a0 --- /dev/null +++ b/.claude/agents/docker/templates/.dockerignore @@ -0,0 +1,85 @@ +# Git +.git +.gitignore +.gitattributes + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Docker +Dockerfile* +docker-compose* +.docker + +# Documentation +*.md +LICENSE +docs/ + +# Environment +.env +.env.* +!.env.example + +# Node.js +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# Build outputs +dist +build +out +*.exe +*.dll +*.so +*.dylib + +# Go +bin/ +vendor/ +*.test +coverage.out +coverage.html + +# Test +__tests__ +*.test.ts +*.test.tsx +*.spec.ts +*.spec.tsx +e2e/ +playwright-report/ +test-results/ + +# Logs +logs +*.log + +# Temp +tmp +temp +.tmp +.temp +.cache + +# CI/CD +.github +.gitlab-ci.yml +.travis.yml +Jenkinsfile + +# Misc +Makefile +*.sh +!entrypoint.sh diff --git a/.claude/agents/docker/templates/Dockerfile.go b/.claude/agents/docker/templates/Dockerfile.go new file mode 100644 index 0000000..37fbb6e --- /dev/null +++ b/.claude/agents/docker/templates/Dockerfile.go @@ -0,0 +1,49 @@ +# === BUILD STAGE === +FROM golang:1.22-alpine AS builder + +WORKDIR /app + +# Instalar dependencias de compilación si necesario +# RUN apk add --no-cache gcc musl-dev + +# Dependencias primero (mejor cache) +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +# Código fuente +COPY . . + +# Build binario estático +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-w -s -extldflags '-static'" \ + -o /app/server ./cmd/server + +# === RUNTIME STAGE === +FROM alpine:3.19 + +# Certificados SSL y timezone +RUN apk --no-cache add ca-certificates tzdata + +WORKDIR /app + +# Copiar binario +COPY --from=builder /app/server . + +# Copiar archivos estáticos/config si necesario +# COPY --from=builder /app/configs ./configs +# COPY --from=builder /app/migrations ./migrations + +# Usuario no-root por seguridad +RUN addgroup -g 1001 -S appgroup && \ + adduser -u 1001 -S appuser -G appgroup +USER appuser + +# Puerto de la aplicación +EXPOSE 8080 + +# Healthcheck +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + +# Punto de entrada +ENTRYPOINT ["./server"] diff --git a/.claude/agents/docker/templates/Dockerfile.react b/.claude/agents/docker/templates/Dockerfile.react new file mode 100644 index 0000000..4a3c234 --- /dev/null +++ b/.claude/agents/docker/templates/Dockerfile.react @@ -0,0 +1,53 @@ +# === BUILD STAGE === +FROM node:22-alpine AS builder + +WORKDIR /app + +# Habilitar pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate + +# Dependencias primero (mejor cache) +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +# Código fuente +COPY . . + +# Variables de entorno para build (pueden ser sobreescritas) +ARG VITE_API_URL=/api +ENV VITE_API_URL=$VITE_API_URL + +# Build de producción +RUN pnpm build + +# === RUNTIME STAGE === +FROM nginx:1.25-alpine + +# Remover config por defecto +RUN rm /etc/nginx/conf.d/default.conf + +# Copiar configuración nginx +COPY nginx.conf /etc/nginx/nginx.conf + +# Copiar archivos estáticos +COPY --from=builder /app/dist /usr/share/nginx/html + +# Permisos correctos +RUN chown -R nginx:nginx /usr/share/nginx/html && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + touch /var/run/nginx.pid && \ + chown -R nginx:nginx /var/run/nginx.pid + +# Usuario no-root +USER nginx + +# Puerto +EXPOSE 80 + +# Healthcheck +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1 + +# Iniciar nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/.claude/agents/docker/templates/deploy.sh b/.claude/agents/docker/templates/deploy.sh new file mode 100755 index 0000000..04c859d --- /dev/null +++ b/.claude/agents/docker/templates/deploy.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# Script de deploy para servidor remoto +# Uso: ./deploy.sh [production|staging] + +set -e + +# ============================================ +# CONFIGURACIÓN +# ============================================ +ENV=${1:-production} +REGISTRY="${REGISTRY:-ghcr.io}" +PROJECT="${PROJECT:-myapp}" +VERSION="${VERSION:-latest}" + +# Servidor remoto +REMOTE_USER="${REMOTE_USER:-deploy}" +REMOTE_HOST="${REMOTE_HOST:-server.example.com}" +REMOTE_PATH="${REMOTE_PATH:-/opt/$PROJECT}" + +# Colores +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } + +# ============================================ +# VALIDACIONES +# ============================================ +log "Validando configuración..." + +if ! command -v docker &> /dev/null; then + error "Docker no está instalado" +fi + +if [ -z "$REMOTE_HOST" ] || [ "$REMOTE_HOST" = "server.example.com" ]; then + error "Configura REMOTE_HOST antes de ejecutar" +fi + +# ============================================ +# BUILD +# ============================================ +log "Building imágenes para $ENV..." + +# Build con buildkit para mejor cache +export DOCKER_BUILDKIT=1 + +docker build -t $REGISTRY/$PROJECT-frontend:$VERSION ./frontend +docker build -t $REGISTRY/$PROJECT-backend:$VERSION ./backend + +log "Imágenes construidas:" +docker images | grep $PROJECT + +# ============================================ +# PUSH A REGISTRY +# ============================================ +log "Pushing imágenes a $REGISTRY..." + +docker push $REGISTRY/$PROJECT-frontend:$VERSION +docker push $REGISTRY/$PROJECT-backend:$VERSION + +log "Imágenes subidas correctamente" + +# ============================================ +# DEPLOY A SERVIDOR +# ============================================ +log "Desplegando a $REMOTE_HOST..." + +# Copiar docker-compose si no existe +ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $REMOTE_PATH" +scp docker-compose.yml $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/ + +# Copiar .env si existe +if [ -f ".env.$ENV" ]; then + scp .env.$ENV $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/.env +fi + +# Deploy remoto +ssh $REMOTE_USER@$REMOTE_HOST << EOF + cd $REMOTE_PATH + + # Pull nuevas imágenes + docker compose pull + + # Restart servicios + docker compose up -d --remove-orphans + + # Limpiar imágenes antiguas + docker image prune -f + + # Verificar estado + docker compose ps +EOF + +# ============================================ +# VERIFICACIÓN +# ============================================ +log "Verificando deploy..." + +sleep 5 + +# Healthcheck +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$REMOTE_HOST/health || echo "000") + +if [ "$HTTP_STATUS" = "200" ]; then + log "Deploy exitoso! Servidor respondiendo correctamente." +else + warn "Servidor respondió con código: $HTTP_STATUS" + warn "Verifica los logs: ssh $REMOTE_USER@$REMOTE_HOST 'docker compose -f $REMOTE_PATH/docker-compose.yml logs'" +fi + +log "Deploy completado para $ENV" diff --git a/.claude/agents/docker/templates/docker-compose.dev.yml b/.claude/agents/docker/templates/docker-compose.dev.yml new file mode 100644 index 0000000..bbf12f8 --- /dev/null +++ b/.claude/agents/docker/templates/docker-compose.dev.yml @@ -0,0 +1,100 @@ +# Docker Compose para DESARROLLO con hot reload +# Uso: docker compose -f docker-compose.dev.yml up + +services: + # ============================================ + # FRONTEND (Vite dev server con hot reload) + # ============================================ + frontend: + image: node:22-alpine + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-frontend-dev + working_dir: /app + command: sh -c "corepack enable && pnpm install && pnpm dev --host 0.0.0.0" + volumes: + - ./frontend:/app + - frontend_node_modules:/app/node_modules + ports: + - "${FRONTEND_PORT:-5173}:5173" + environment: + - VITE_API_URL=http://localhost:${BACKEND_PORT:-8080} + depends_on: + - backend + networks: + - app-network + + # ============================================ + # BACKEND (Go con air para hot reload) + # ============================================ + backend: + image: cosmtrek/air:latest + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-backend-dev + working_dir: /app + volumes: + - ./backend:/app + - go_mod_cache:/go/pkg/mod + ports: + - "${BACKEND_PORT:-8080}:8080" + environment: + - DATABASE_URL=postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-secret}@db:5432/${POSTGRES_DB:-app}?sslmode=disable + - REDIS_URL=redis://redis:6379 + - ENV=development + depends_on: + db: + condition: service_healthy + networks: + - app-network + + # ============================================ + # DATABASE (PostgreSQL) + # ============================================ + db: + image: postgres:16-alpine + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-db-dev + environment: + POSTGRES_USER: ${POSTGRES_USER:-app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secret} + POSTGRES_DB: ${POSTGRES_DB:-app} + volumes: + - postgres_data_dev:/var/lib/postgresql/data + ports: + - "${POSTGRES_PORT:-5432}:5432" + networks: + - app-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-app}"] + interval: 5s + timeout: 5s + retries: 5 + + # ============================================ + # CACHE (Redis) + # ============================================ + redis: + image: redis:7-alpine + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-redis-dev + ports: + - "${REDIS_PORT:-6379}:6379" + networks: + - app-network + + # ============================================ + # ADMINER (UI para DB - opcional) + # ============================================ + adminer: + image: adminer:latest + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-adminer + ports: + - "8081:8080" + depends_on: + - db + networks: + - app-network + +networks: + app-network: + driver: bridge + +volumes: + postgres_data_dev: + frontend_node_modules: + go_mod_cache: diff --git a/.claude/agents/docker/templates/docker-compose.fullstack.yml b/.claude/agents/docker/templates/docker-compose.fullstack.yml new file mode 100644 index 0000000..fead939 --- /dev/null +++ b/.claude/agents/docker/templates/docker-compose.fullstack.yml @@ -0,0 +1,119 @@ +# Docker Compose para stack completo: Frontend + Backend + DB +# Uso: docker compose -f docker-compose.yml up -d + +services: + # ============================================ + # FRONTEND (React/Vite + Nginx) + # ============================================ + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + VITE_API_URL: /api + image: ${COMPOSE_PROJECT_NAME:-myapp}-frontend:latest + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-frontend + restart: unless-stopped + ports: + - "${FRONTEND_PORT:-3000}:80" + depends_on: + backend: + condition: service_healthy + networks: + - app-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80"] + interval: 30s + timeout: 5s + retries: 3 + + # ============================================ + # BACKEND (Go) + # ============================================ + backend: + build: + context: ./backend + dockerfile: Dockerfile + image: ${COMPOSE_PROJECT_NAME:-myapp}-backend:latest + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-backend + restart: unless-stopped + ports: + - "${BACKEND_PORT:-8080}:8080" + environment: + - DATABASE_URL=postgres://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-secret}@db:5432/${POSTGRES_DB:-app}?sslmode=disable + - REDIS_URL=redis://redis:6379 + - JWT_SECRET=${JWT_SECRET:-change-me-in-production} + - ENV=${ENV:-production} + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + networks: + - app-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + + # ============================================ + # DATABASE (PostgreSQL) + # ============================================ + db: + image: postgres:16-alpine + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-db + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secret} + POSTGRES_DB: ${POSTGRES_DB:-app} + volumes: + - postgres_data:/var/lib/postgresql/data + # Opcional: scripts de inicialización + # - ./init.sql:/docker-entrypoint-initdb.d/init.sql + ports: + - "${POSTGRES_PORT:-5432}:5432" + networks: + - app-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-app} -d ${POSTGRES_DB:-app}"] + interval: 10s + timeout: 5s + retries: 5 + + # ============================================ + # CACHE (Redis) + # ============================================ + redis: + image: redis:7-alpine + container_name: ${COMPOSE_PROJECT_NAME:-myapp}-redis + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - redis_data:/data + ports: + - "${REDIS_PORT:-6379}:6379" + networks: + - app-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +# ============================================ +# NETWORKS +# ============================================ +networks: + app-network: + driver: bridge + +# ============================================ +# VOLUMES +# ============================================ +volumes: + postgres_data: + name: ${COMPOSE_PROJECT_NAME:-myapp}_postgres_data + redis_data: + name: ${COMPOSE_PROJECT_NAME:-myapp}_redis_data diff --git a/.claude/agents/docker/templates/nginx.conf b/.claude/agents/docker/templates/nginx.conf new file mode 100644 index 0000000..6ca74e2 --- /dev/null +++ b/.claude/agents/docker/templates/nginx.conf @@ -0,0 +1,118 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + + # Performance + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Buffer sizes + client_body_buffer_size 10K; + client_header_buffer_size 1k; + client_max_body_size 8m; + large_client_header_buffers 4 4k; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/json + application/xml + application/rss+xml + application/atom+xml + image/svg+xml; + + server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # SPA: todas las rutas van a index.html + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy para API backend + location /api/ { + proxy_pass http://backend:8080/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + proxy_read_timeout 90s; + } + + # WebSocket proxy + location /ws { + proxy_pass http://backend:8080/ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 86400; + } + + # Cache agresivo para assets estáticos + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # No cachear index.html ni manifest + location ~* \.(html|json)$ { + expires -1; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Ocultar versión de nginx + server_tokens off; + + # Páginas de error + error_page 404 /index.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +}