Add production setup and update Docker configurations

- Introduced a new docker-compose.prod.yml for production deployment.
- Updated docker-compose.yml to change backend port mapping.
- Added svelte-sonner dependency to frontend package.json.
- Created Dockerfile.prod for building the frontend application.
- Enhanced README.md with production deployment instructions.
This commit is contained in:
AlexeerCT
2025-12-26 09:48:39 -06:00
parent d3a0086188
commit ae1cf83491
6 changed files with 357 additions and 2 deletions

View File

@@ -132,6 +132,21 @@ python -c "from core.database import init_db; init_db()"
- **Frontend**: http://localhost:5173
- **Keycloak Admin**: http://localhost:8080
## 🛠️ Produccion
```
docker build -t dev.aduanasoft.com/anexo76/backend:latest -f ./backend/Dockerfile ./backend
docker build -t dev.aduanasoft.com/anexo76/frontend:latest -f ./frontend/Dockerfile.prod ./frontend
```
Publica en el registro (ajusta el registry si corresponde):
```
docker login dev.aduanasoft.com
docker push dev.aduanasoft.com/anexo76/backend:latest
docker push dev.aduanasoft.com/anexo76/frontend:latest
```
## 🛠️ Desarrollo Local
### Backend

260
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,260 @@
services:
# PostgreSQL - Base de datos core (app)
postgres-a76:
image: postgres:18-alpine
container_name: anexo76-postgres-a76
environment:
POSTGRES_DB: anexo76_core
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_APP_PASSWORD:-postgres}
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
ports:
- "5432:5432"
volumes:
- postgres_app_data:/var/lib/postgresql/data
- ./scripts/postgres-app-entrypoint.sh:/docker-entrypoint-initdb.d/init-app.sh:ro
networks:
- backend-net
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d anexo76_core || exit 1"]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
shm_size: 128mb
# PostgreSQL - Base de datos Keycloak
postgres-keycloak:
image: postgres:18-alpine
container_name: anexo76-postgres-keycloak
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_KEYCLOAK_PASSWORD:-postgres}
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
ports:
- "5433:5432"
volumes:
- postgres_keycloak_data:/var/lib/postgresql/data
- ./scripts/postgres-keycloak-entrypoint.sh:/docker-entrypoint-initdb.d/init-keycloak.sh:ro
networks:
- auth-net
- backend-net
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d keycloak || exit 1"]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
shm_size: 128mb
# Keycloak - Servidor de autenticación
keycloak:
image: quay.io/keycloak/keycloak:26.4
container_name: anexo76-keycloak
environment:
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
KC_DB: postgres
KC_DB_URL_HOST: postgres-keycloak
KC_DB_URL_PORT: "5432"
KC_DB_URL_DATABASE: keycloak
KC_DB_URL: jdbc:postgresql://postgres-keycloak:5432/keycloak
KC_DB_USERNAME: postgres
KC_DB_PASSWORD: ${POSTGRES_KEYCLOAK_PASSWORD:-postgres}
KC_DB_SCHEMA: public
KC_HOSTNAME: localhost
KC_HTTP_ENABLED: "true"
KC_HOSTNAME_STRICT: "false"
KC_HOSTNAME_STRICT_HTTPS: "false"
KC_PROXY_HEADERS: "xforwarded"
KC_HEALTH_ENABLED: "true"
KC_METRICS_ENABLED: "true"
KC_LOG_LEVEL: INFO
JAVA_OPTS_APPEND: "-Xms256m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true"
command:
- start-dev
- --db=postgres
- --db-url-host=postgres-keycloak
- --db-url-port=5432
- --db-url-database=keycloak
- --db-username=postgres
- --db-password=${POSTGRES_KEYCLOAK_PASSWORD:-postgres}
- --http-enabled=true
- --hostname-strict=false
- --proxy-headers=xforwarded
ports:
- "8080:8080"
- "9000:9000"
depends_on:
postgres-keycloak:
condition: service_healthy
volumes:
- keycloak_data:/opt/keycloak/data
networks:
- auth-net
- backend-net
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000; echo -e 'GET /health/ready HTTP/1.1\r\nhost: 127.0.0.1\r\nConnection: close\r\n\r\n' >&3; grep -q 'HTTP/1.1 200' <&3 || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 90s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 768M
reservations:
memory: 512M
# Backend - FastAPI
backend:
image: dev.aduanasoft.com/anexo76/backend:latest
container_name: anexo76-backend
environment:
- DEBUG=${DEBUG:-True}
- ENVIRONMENT=${ENVIRONMENT:-development}
- PYTHONUNBUFFERED=1
- PYTHONDONTWRITEBYTECODE=1
- CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76}
- CORE_DB_PORT=${CORE_DB_PORT:-5432}
- CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core}
- CORE_DB_USER=${CORE_DB_USER:-postgres}
- CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres}
- KEYCLOAK_SERVER_URL=${KEYCLOAK_SERVER_URL:-http://keycloak:8080}
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend}
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret}
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000}
ports:
- "8000:8000"
depends_on:
postgres-a76:
condition: service_healthy
keycloak:
condition: service_healthy
volumes:
- ./backend:/app
- backend_cache:/app/__pycache__
- ./scripts/backend-entrypoint.sh:/entrypoint.sh:ro
networks:
- backend-net
- frontend-net
restart: unless-stopped
entrypoint: ["/entrypoint.sh"]
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--log-level", "info"]
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Frontend - SvelteKit
frontend:
image: dev.aduanasoft.com/anexo76/frontend:latest
container_name: anexo76-frontend
environment:
- NODE_ENV=${NODE_ENV:-development}
- VITE_API_URL=${VITE_API_URL:-http://localhost:8000/api/}
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/}
- VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-http://localhost:8080}
- VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master}
- VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend}
- KEYCLOAK_URL=${KEYCLOAK_URL:-http://keycloak:8080}
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-master}
- KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-backend}
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-zRU5NuvUFtBSOuh7Kdc372AItoWGLgz9}
ports:
- "5173:5173"
depends_on:
backend:
condition: service_healthy
entrypoint: ["/frontend-entrypoint.sh"]
volumes:
- ./frontend:/app
- frontend_node_modules:/app/node_modules
- ./scripts/frontend-entrypoint.sh:/frontend-entrypoint.sh:ro
networks:
- frontend-net
- auth-net
restart: unless-stopped
command: ["pnpm", "run", "dev", "--", "--host", "0.0.0.0"]
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5173/ || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 45s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
volumes:
postgres_app_data:
driver: local
postgres_keycloak_data:
driver: local
keycloak_data:
driver: local
frontend_node_modules:
driver: local
backend_cache:
driver: local
networks:
backend-net:
driver: bridge
auth-net:
driver: bridge
frontend-net:
driver: bridge

View File

@@ -162,7 +162,7 @@ services:
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret}
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000}
ports:
- "5050:8000"
- "8000:8000"
depends_on:
postgres-a76:
condition: service_healthy

56
frontend/Dockerfile.prod Normal file
View File

@@ -0,0 +1,56 @@
# ==========================
# Etapa de build
# ==========================
FROM node:22-alpine AS build
# Directorio de trabajo
WORKDIR /app
# Configurar npm para trabajar con certificados autofirmados e instalar pnpm
RUN npm config set strict-ssl false && \
npm install -g pnpm
# Copiar archivos de dependencias
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
# Instalar dependencias con pnpm
RUN pnpm install --frozen-lockfile
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}
# Copiar el resto del código
COPY . .
# Construir el proyecto
RUN pnpm run build
# ==========================
# Etapa de ejecución con Node.js
# ==========================
FROM node:22-alpine AS runtime
WORKDIR /app
# Crear usuario no-root para seguridad antes de copiar con --chown
RUN addgroup -g 1001 -S nodejs
RUN adduser -S svelte -u 1001
# Copiar solo archivos necesarios para producción y aplicar propietario en la copia
COPY --from=build --chown=svelte:nodejs /app/build ./build
COPY --from=build --chown=svelte:nodejs /app/package.json ./
COPY --from=build --chown=svelte:nodejs /app/node_modules ./node_modules
USER svelte
# Puerto para SvelteKit con adapter-node
EXPOSE 3000
# Variables de entorno
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0
# Ejecutar aplicación con Node.js
CMD ["node", "build"]

View File

@@ -56,6 +56,7 @@
},
"dependencies": {
"keycloak-js": "^26.2.1",
"lucide-svelte": "^0.553.0"
"lucide-svelte": "^0.553.0",
"svelte-sonner": "^1.0.7"
}
}

View File

@@ -14,6 +14,9 @@ importers:
lucide-svelte:
specifier: ^0.553.0
version: 0.553.0(svelte@5.40.2)
svelte-sonner:
specifier: ^1.0.7
version: 1.0.7(svelte@5.40.2)
devDependencies:
'@eslint/compat':
specifier: ^1.4.0
@@ -1675,6 +1678,11 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
runed@0.28.0:
resolution: {integrity: sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==}
peerDependencies:
svelte: ^5.7.0
runed@0.35.1:
resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==}
peerDependencies:
@@ -1761,6 +1769,11 @@ packages:
svelte:
optional: true
svelte-sonner@1.0.7:
resolution: {integrity: sha512-1EUFYmd7q/xfs2qCHwJzGPh9n5VJ3X6QjBN10fof2vxgy8fYE7kVfZ7uGnd7i6fQaWIr5KvXcwYXE/cmTEjk5A==}
peerDependencies:
svelte: ^5.0.0
svelte-toolbelt@0.10.6:
resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==}
engines: {node: '>=18', pnpm: '>=8.7.0'}
@@ -3379,6 +3392,11 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
runed@0.28.0(svelte@5.40.2):
dependencies:
esm-env: 1.2.2
svelte: 5.40.2
runed@0.35.1(@sveltejs/kit@2.47.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.40.2)(vite@7.1.10(@types/node@20.19.22)(jiti@2.6.1)(lightningcss@1.30.1)))(svelte@5.40.2)(vite@7.1.10(@types/node@20.19.22)(jiti@2.6.1)(lightningcss@1.30.1)))(svelte@5.40.2):
dependencies:
dequal: 2.0.3
@@ -3460,6 +3478,11 @@ snapshots:
optionalDependencies:
svelte: 5.40.2
svelte-sonner@1.0.7(svelte@5.40.2):
dependencies:
runed: 0.28.0(svelte@5.40.2)
svelte: 5.40.2
svelte-toolbelt@0.10.6(@sveltejs/kit@2.47.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.40.2)(vite@7.1.10(@types/node@20.19.22)(jiti@2.6.1)(lightningcss@1.30.1)))(svelte@5.40.2)(vite@7.1.10(@types/node@20.19.22)(jiti@2.6.1)(lightningcss@1.30.1)))(svelte@5.40.2):
dependencies:
clsx: 2.1.1