diff --git a/.env.prod.local b/.env.prod.local new file mode 100644 index 0000000..73e4832 --- /dev/null +++ b/.env.prod.local @@ -0,0 +1,17 @@ +# Variables de Entorno para Prueba de Producción Local + +# Database +POSTGRES_DB=checklist_db +POSTGRES_USER=checklist_user +POSTGRES_PASSWORD=checklist_pass_2024_prod + +# Backend +SECRET_KEY=production-secret-key-super-segura-minimo-32-caracteres-123456 +OPENAI_API_KEY= +ENVIRONMENT=production + +# CORS - Para prueba local +ALLOWED_ORIGINS=http://localhost,http://localhost:80,http://127.0.0.1 + +# Frontend - Para prueba local (apunta al backend en puerto 8000) +VITE_API_URL=http://localhost:8000 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..2947d1b --- /dev/null +++ b/.env.production @@ -0,0 +1,17 @@ +# Variables de Entorno para Producción - Dockploy + +# Database +POSTGRES_DB=checklist_db +POSTGRES_USER=checklist_user +POSTGRES_PASSWORD=CAMBIAR-PASSWORD-SUPER-SEGURA-AQUI + +# Backend +SECRET_KEY=CAMBIAR-CLAVE-SECRETA-MINIMO-32-CARACTERES-SUPER-SEGURA +OPENAI_API_KEY=tu-openai-api-key-aqui +ENVIRONMENT=production + +# CORS - Separar múltiples dominios con comas +ALLOWED_ORIGINS=https://tudominio.com,https://www.tudominio.com,https://app.tudominio.com + +# Frontend +VITE_API_URL=https://api.tudominio.com diff --git a/DEPLOY_DOCKPLOY.md b/DEPLOY_DOCKPLOY.md new file mode 100644 index 0000000..0219d9a --- /dev/null +++ b/DEPLOY_DOCKPLOY.md @@ -0,0 +1,256 @@ +# Guía de Deployment en Dockploy + +## Arquitectura de Conexión + +### Desarrollo (Docker Compose Local) +``` +Frontend (localhost:5173) → Backend (localhost:8000) → PostgreSQL (localhost:5432) +``` + +### Producción (Dockploy) +``` +Frontend (tudominio.com) → Backend (api.tudominio.com) → PostgreSQL (interno) +``` + +--- + +## 📋 Pasos para Deploy en Dockploy + +### 1. Preparar Variables de Entorno + +**Backend (.env)** +```bash +DATABASE_URL=postgresql://user:pass@postgres:5432/checklist_db +SECRET_KEY=tu-clave-secreta-minimo-32-caracteres-super-segura +OPENAI_API_KEY=tu-api-key-de-openai +ENVIRONMENT=production +ALLOWED_ORIGINS=https://tudominio.com,https://www.tudominio.com +``` + +**Frontend (.env)** +```bash +VITE_API_URL=https://api.tudominio.com +``` + +### 2. Actualizar CORS en Backend + +Editar `backend/app/main.py`: +```python +import os + +# CORS +allowed_origins = os.getenv("ALLOWED_ORIGINS", "http://localhost:5173").split(",") +app.add_middleware( + CORSMiddleware, + allow_origins=allowed_origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +``` + +### 3. Crear Dockerfile de Producción para Frontend + +Crear `frontend/Dockerfile.prod`: +```dockerfile +FROM node:18-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production + +COPY . . +ARG VITE_API_URL +ENV VITE_API_URL=$VITE_API_URL + +RUN npm run build + +# Servidor Nginx para servir archivos estáticos +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;"] +``` + +Crear `frontend/nginx.conf`: +```nginx +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + # Configuración de cache para assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} +``` + +### 4. Estructura Recomendada en Dockploy + +**Opción A: Servicios Separados (Recomendado)** +- **Servicio 1**: PostgreSQL (Base de datos interna) +- **Servicio 2**: Backend API (api.tudominio.com) +- **Servicio 3**: Frontend (tudominio.com) + +**Opción B: Docker Compose en Dockploy** +Usar `docker-compose.prod.yml`: +```yaml +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - app-network + + backend: + build: ./backend + environment: + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} + SECRET_KEY: ${SECRET_KEY} + OPENAI_API_KEY: ${OPENAI_API_KEY} + ENVIRONMENT: production + ALLOWED_ORIGINS: ${ALLOWED_ORIGINS} + ports: + - "8000:8000" + depends_on: + - postgres + networks: + - app-network + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile.prod + args: + VITE_API_URL: ${VITE_API_URL} + ports: + - "80:80" + depends_on: + - backend + networks: + - app-network + +volumes: + postgres_data: + +networks: + app-network: + driver: bridge +``` + +### 5. Configuración de Dominios en Dockploy + +1. **Backend API**: + - Dominio: `api.tudominio.com` + - Puerto interno: `8000` + - Habilitar SSL/TLS + +2. **Frontend**: + - Dominio: `tudominio.com` + - Puerto interno: `80` + - Habilitar SSL/TLS + +3. **PostgreSQL**: + - Solo acceso interno (no exponer públicamente) + +### 6. Scripts de Migración + +Crear `backend/migrate.sh`: +```bash +#!/bin/bash +# Ejecutar migraciones en producción +python -c "from app.core.database import Base, engine; Base.metadata.create_all(bind=engine)" +``` + +### 7. Health Checks + +El backend ya tiene el endpoint de health. Dockploy puede monitorearlo: +``` +GET https://api.tudominio.com/docs +``` + +--- + +## 🔐 Seguridad en Producción + +1. **Variables de Entorno**: + - Usar secretos de Dockploy, NO hardcodear + - Cambiar `SECRET_KEY` a algo seguro (min 32 caracteres) + - No exponer `DATABASE_URL` públicamente + +2. **HTTPS**: + - Dockploy maneja SSL automáticamente con Let's Encrypt + - Asegurar que CORS solo acepte HTTPS en producción + +3. **Database**: + - NO exponer puerto 5432 públicamente + - Usar credenciales fuertes + - Configurar backups automáticos + +--- + +## 📝 Checklist Pre-Deploy + +- [ ] Cambiar `SECRET_KEY` en variables de entorno +- [ ] Configurar `VITE_API_URL` con dominio de producción +- [ ] Actualizar CORS con dominios de producción +- [ ] Crear Dockerfile.prod para frontend +- [ ] Configurar nginx.conf +- [ ] Probar build local: `docker-compose -f docker-compose.prod.yml up` +- [ ] Configurar dominios en Dockploy +- [ ] Habilitar SSL/TLS en ambos servicios +- [ ] Crear usuario admin inicial en producción +- [ ] Configurar backups de PostgreSQL + +--- + +## 🎯 Comandos Útiles + +**Build local de producción:** +```bash +docker-compose -f docker-compose.prod.yml build +docker-compose -f docker-compose.prod.yml up -d +``` + +**Ver logs:** +```bash +docker-compose -f docker-compose.prod.yml logs -f backend +docker-compose -f docker-compose.prod.yml logs -f frontend +``` + +**Crear usuario admin en producción:** +```bash +docker-compose -f docker-compose.prod.yml exec backend python -c " +from app.core.database import SessionLocal +from app.core.security import get_password_hash +from app.models import User + +db = SessionLocal() +admin = User( + username='admin', + email='admin@tudominio.com', + full_name='Administrador', + hashed_password=get_password_hash('tu-password-segura'), + role='admin', + is_active=True +) +db.add(admin) +db.commit() +print('Admin creado exitosamente') +" +``` diff --git a/backend/app/main.py b/backend/app/main.py index 4ef7bbb..429271f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -16,10 +16,11 @@ Base.metadata.create_all(bind=engine) app = FastAPI(title="Checklist Inteligente API", version="1.0.0") -# CORS +# CORS - Configuración dinámica para desarrollo y producción +allowed_origins = os.getenv("ALLOWED_ORIGINS", "http://localhost:5173,http://localhost:3000").split(",") app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:5173", "http://localhost:3000"], + allow_origins=allowed_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..e3d6612 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,64 @@ +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + container_name: checklist-db-prod + environment: + POSTGRES_DB: ${POSTGRES_DB:-checklist_db} + POSTGRES_USER: ${POSTGRES_USER:-checklist_user} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - app-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-checklist_user}"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: checklist-backend-prod + environment: + DATABASE_URL: postgresql://${POSTGRES_USER:-checklist_user}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-checklist_db} + SECRET_KEY: ${SECRET_KEY} + OPENAI_API_KEY: ${OPENAI_API_KEY} + ENVIRONMENT: production + ALLOWED_ORIGINS: ${ALLOWED_ORIGINS} + ports: + - "8000:8000" + volumes: + - ./uploads:/app/uploads + depends_on: + postgres: + condition: service_healthy + networks: + - app-network + restart: unless-stopped + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile.prod + args: + VITE_API_URL: ${VITE_API_URL} + container_name: checklist-frontend-prod + ports: + - "80:80" + depends_on: + - backend + networks: + - app-network + restart: unless-stopped + +volumes: + postgres_data: + +networks: + app-network: + driver: bridge diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod new file mode 100644 index 0000000..d4bb4c1 --- /dev/null +++ b/frontend/Dockerfile.prod @@ -0,0 +1,34 @@ +FROM node:18-alpine AS builder + +WORKDIR /app + +# Copiar package files +COPY package*.json ./ + +# Instalar todas las dependencias (necesitamos las dev para el build) +RUN npm install + +# Copiar código +COPY . . + +# Build argument para la URL de la API +ARG VITE_API_URL +ENV VITE_API_URL=$VITE_API_URL + +# Construir la aplicación +RUN npm run build + +# Etapa de producción con Nginx +FROM nginx:alpine + +# Copiar archivos construidos +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copiar configuración de nginx +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Exponer puerto 80 +EXPOSE 80 + +# Comando para iniciar nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..4873d00 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,36 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript; + + # SPA routing - todas las rutas van a index.html + location / { + try_files $uri $uri/ /index.html; + } + + # Cache 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"; + } + + # No cache para index.html + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires 0; + } + + # 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; +}