9 Commits

Author SHA1 Message Date
nietzshn 4c972e1be2 Merge pull request 'chore: update Gitea workflow summaries to use GITHUB_STEP_SUMMARY, switch to env variables, and add QA notification step' (#2) from dev into staging
CI Pipeline / HTML Lint (push) Successful in 7s
Deploy Staging / Build and Push (push) Successful in 14s
CI Pipeline / Build Docker Image (push) Successful in 57s
CI Pipeline / Security Scan (push) Successful in 10s
Deploy Staging / Notification (push) Successful in 1s
Deploy Staging / Deploy to Staging (push) Successful in 9s
CI Pipeline / HTML Lint (pull_request) Successful in 9s
CI Pipeline / Build Docker Image (pull_request) Successful in 57s
CI Pipeline / Security Scan (pull_request) Successful in 10s
Reviewed-on: #2
2026-06-02 22:25:33 -06:00
nietzshn 08c40162b0 Merge branch 'staging' into dev
CI Pipeline / HTML Lint (push) Successful in 7s
Deploy QA / Build and Push (push) Successful in 14s
CI Pipeline / HTML Lint (pull_request) Successful in 7s
CI Pipeline / Build Docker Image (push) Successful in 58s
Deploy QA / Deploy to QA (push) Successful in 8s
CI Pipeline / Build Docker Image (pull_request) Successful in 56s
Deploy QA / Notification (push) Successful in 1s
CI Pipeline / Security Scan (pull_request) Successful in 11s
CI Pipeline / Security Scan (push) Successful in 11s
2026-06-02 22:25:24 -06:00
nietzshn f48307410e chore: update Gitea workflow summaries to use GITHUB_STEP_SUMMARY, switch to env variables, and add QA notification step
CI Pipeline / HTML Lint (push) Successful in 7s
Deploy QA / Build and Push (push) Successful in 14s
CI Pipeline / Build Docker Image (push) Successful in 58s
Deploy QA / Deploy to QA (push) Successful in 9s
CI Pipeline / Security Scan (push) Successful in 11s
Deploy QA / Notification (push) Successful in 1s
CI Pipeline / HTML Lint (pull_request) Successful in 9s
CI Pipeline / Build Docker Image (pull_request) Successful in 57s
CI Pipeline / Security Scan (pull_request) Successful in 12s
2026-06-02 22:09:30 -06:00
nietzshn 5d85b4b741 Merge pull request 'feat: add initial multi-environment CI/CD pipeline POC' (#1) from dev into staging
CI Pipeline / HTML Lint (push) Successful in 7s
Deploy Staging / Build and Push (push) Successful in 15s
CI Pipeline / Build Docker Image (push) Successful in 1m0s
Deploy Staging / Deploy to Staging (push) Successful in 8s
CI Pipeline / Security Scan (push) Successful in 11s
Deploy Staging / Notification (push) Failing after 1s
Reviewed-on: #1
2026-06-02 22:04:02 -06:00
nietzshn d4c3affa2f feat: implement dynamic runtime environment configuration via env-config.js injection and update project documentation
CI Pipeline / HTML Lint (push) Successful in 8s
Deploy QA / Build and Push (push) Successful in 16s
CI Pipeline / Build Docker Image (push) Successful in 1m7s
Deploy QA / Deploy to QA (push) Successful in 9s
CI Pipeline / Security Scan (push) Successful in 12s
CI Pipeline / HTML Lint (pull_request) Successful in 8s
CI Pipeline / Build Docker Image (pull_request) Successful in 58s
CI Pipeline / Security Scan (pull_request) Successful in 12s
2026-06-02 21:59:20 -06:00
nietzshn 0a798cf3b0 refactor: replace appleboy/ssh-action with native ssh command execution in deployment workflows
CI Pipeline / HTML Lint (push) Successful in 8s
Deploy QA / Build and Push (push) Successful in 15s
CI Pipeline / Build Docker Image (push) Successful in 57s
Deploy QA / Deploy to QA (push) Successful in 8s
CI Pipeline / Security Scan (push) Successful in 11s
2026-06-02 21:50:48 -06:00
nietzshn 252fbe5003 refactor: switch ssh remote variable passing to positional arguments for deploy-qa pipeline
CI Pipeline / HTML Lint (push) Successful in 11s
Deploy QA / Build and Push (push) Successful in 21s
CI Pipeline / Build Docker Image (push) Successful in 1m3s
Deploy QA / Deploy to QA (push) Successful in 9s
CI Pipeline / Security Scan (push) Successful in 21s
2026-06-02 21:11:41 -06:00
nietzshn dc86eb2bf2 ci: use ssh-agent with dedicated deploy key (no passphrase) 2026-06-01 22:40:59 -06:00
nietzshn 87faff525c ci: use base64-encoded SSH key to preserve newlines 2026-06-01 22:38:17 -06:00
5 changed files with 184 additions and 104 deletions
+38 -19
View File
@@ -66,34 +66,52 @@ jobs:
url: https://practicas.prod.kubistudio.cloud
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USERNAME }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
run: |
set -euo pipefail
IMAGE_TAG="${{ needs.build-and-push.outputs.image_tag }}"
APP_VERSION="${{ needs.build-and-push.outputs.app_version }}"
eval $(ssh-agent -s)
echo "${{ secrets.DEPLOY_SSH_KEY }}" | ssh-add -
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
# 1. Pasamos las variables como argumentos en el mismo orden
ssh ${{ secrets.DEPLOY_USERNAME }}@${{ secrets.DEPLOY_HOST }} bash -s \
"${{ env.REGISTRY_URL }}" \
"${{ env.IMAGE_NAME }}" \
"${IMAGE_TAG}" \
"${APP_VERSION}" \
"${{ gitea.actor }}" \
"${{ secrets.TOKEN }}" << 'EOF'
set -euo pipefail
# 2. Las recibimos dentro de la sesión remota
REGISTRY_URL=$1
IMAGE_NAME=$2
IMAGE_TAG=$3
APP_VERSION=$4
GITEA_ACTOR=$5
TOKEN=$6
echo "Saving current image tag for rollback..."
CURRENT_IMAGE=$(docker inspect cicd-prod --format '{{.Config.Image}}' 2>/dev/null || echo "")
echo "Pulling image..."
echo "${{ secrets.TOKEN }}" | docker login ${{ env.REGISTRY_URL }} -u ${{ gitea.actor }} --password-stdin
docker pull ${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.image_tag }}
echo "$TOKEN" | docker login $REGISTRY_URL -u $GITEA_ACTOR --password-stdin
docker pull $REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
echo "Stopping existing container..."
docker stop cicd-prod 2>/dev/null || true
docker rm cicd-prod 2>/dev/null || true
echo "Starting new container..."
docker run -d \
--name cicd-prod \
--restart unless-stopped \
-p 8083:80 \
docker run -d --name cicd-prod --restart unless-stopped -p 8083:80 \
-e APP_ENV=production \
-e APP_VERSION=${{ needs.build-and-push.outputs.app_version }} \
-e APP_VERSION=${APP_VERSION} \
-e DEPLOY_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.image_tag }}
$REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
echo "Waiting for health check..."
HEALTHY=false
@@ -129,6 +147,7 @@ jobs:
exit 1
fi
EOF
release-notes:
name: Release Notes
@@ -141,27 +160,27 @@ jobs:
set -euo pipefail
COMMITS_SINCE_LAST=$(git log --oneline --no-decorate ${{ gitea.sha }} -n 20 2>/dev/null || echo "No commit history available")
cat << 'SUMMARY' >> $GITEA_HOME/workflow/summary
cat << 'SUMMARY' >> $GITHUB_STEP_SUMMARY
## Production Deployment Successful :rocket:
**Version:** ${{ needs.build-and-push.outputs.app_version }}
**Commit:** ${{ gitea.sha }}
**Image:** ${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:production-latest
**Image:** ${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:production-latest
**URL:** https://practicas.prod.kubistudio.cloud
### Recent Commits
```
SUMMARY
git log --oneline --no-decorate -n 20 ${{ gitea.sha }} 2>/dev/null >> $GITEA_HOME/workflow/summary || true
git log --oneline --no-decorate -n 20 ${{ gitea.sha }} 2>/dev/null >> $GITHUB_STEP_SUMMARY || true
cat << 'SUMMARY' >> $GITEA_HOME/workflow/summary
cat << 'SUMMARY' >> $GITHUB_STEP_SUMMARY
```
### Rollback
If needed, rollback with:
```bash
docker stop cicd-prod && docker rm cicd-prod
docker run -d --name cicd-prod --restart unless-stopped -p 8083:80 \${{ vars.REGISTRY_URL }}/\${{ vars.IMAGE_NAME }}:stable
docker run -d --name cicd-prod --restart unless-stopped -p 8083:80 \${{ env.REGISTRY_URL }}/\${{ env.IMAGE_NAME }}:stable
```
SUMMARY
+43 -19
View File
@@ -55,40 +55,48 @@ jobs:
needs: build-and-push
steps:
- name: Deploy via SSH
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_PASSPHRASE: ${{ secrets.DEPLOY_PASSPHRASE }}
run: |
set -euo pipefail
IMAGE_TAG="${{ needs.build-and-push.outputs.image_tag }}"
printf '%s\n' "$DEPLOY_SSH_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf '%s\n' "$DEPLOY_PASSPHRASE" > /tmp/passphrase
sudo apt-get update -qq && sudo apt-get install -y -qq sshpass
eval $(ssh-agent -s)
echo "${{ secrets.DEPLOY_SSH_KEY }}" | ssh-add -
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
sshpass -f /tmp/passphrase ssh -i /tmp/deploy_key \
-o StrictHostKeyChecking=no \
${{ secrets.DEPLOY_USERNAME }}@${{ secrets.DEPLOY_HOST }} bash -s \
-e REGISTRY_URL=${{ env.REGISTRY_URL }} \
-e IMAGE_NAME=${{ env.IMAGE_NAME }} \
-e IMAGE_TAG=${IMAGE_TAG} \
-e GIT_SHA=${{ gitea.sha }} \
-e GIT_BRANCH=dev \
-e GITEA_ACTOR=${{ gitea.actor }} \
-e BUILD_NUMBER=${{ gitea.run_id }} \
-e TOKEN=${{ secrets.TOKEN }} << 'EOF'
# 1. Pasamos las variables como argumentos en el mismo orden
ssh ${{ secrets.DEPLOY_USERNAME }}@${{ secrets.DEPLOY_HOST }} bash -s \
"${{ env.REGISTRY_URL }}" \
"${{ env.IMAGE_NAME }}" \
"${IMAGE_TAG}" \
"${{ gitea.sha }}" \
"${{ gitea.actor }}" \
"${{ gitea.run_id }}" \
"${{ secrets.TOKEN }}" << 'EOF'
set -euo pipefail
# 2. Las recibimos dentro de la sesión remota
REGISTRY_URL=$1
IMAGE_NAME=$2
IMAGE_TAG=$3
GIT_SHA=$4
GITEA_ACTOR=$5
BUILD_NUMBER=$6
TOKEN=$7
# Variables locales del script
GIT_BRANCH="dev"
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Pulling image..."
echo "$TOKEN" | docker login $REGISTRY_URL -u $GITEA_ACTOR --password-stdin
docker pull $REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
echo "Stopping existing container..."
docker stop cicd-qa 2>/dev/null || true
docker rm cicd-qa 2>/dev/null || true
echo "Starting new container..."
docker run -d --name cicd-qa --restart unless-stopped -p 8081:80 \
-e APP_ENV=qa \
@@ -99,6 +107,7 @@ jobs:
-e DEPLOY_TIME=${BUILD_DATE} \
-e BUILD_NUMBER=${BUILD_NUMBER} \
$REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
echo "Waiting for health check..."
for i in $(seq 1 12); do
if curl -sf http://localhost:8081/health > /dev/null 2>&1; then
@@ -111,4 +120,19 @@ jobs:
exit 1
EOF
notify:
name: Notification
runs-on: ubuntu-latest
needs: [build-and-push, deploy]
if: always()
steps:
- name: Write summary
run: |
cat << 'SUMMARY' >> $GITHUB_STEP_SUMMARY
## QA Deployment ${{ needs.deploy.result }}
**Branch:** dev
**Commit:** ${{ gitea.sha }}
**Image:** ${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:qa-latest
**URL:** https://practicas.qa.kubistudio.cloud
SUMMARY
+65 -34
View File
@@ -55,47 +55,78 @@ jobs:
needs: build-and-push
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USERNAME }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
run: |
set -euo pipefail
IMAGE_TAG="${{ needs.build-and-push.outputs.image_tag }}"
eval $(ssh-agent -s)
echo "${{ secrets.DEPLOY_SSH_KEY }}" | ssh-add -
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
# 1. Pasamos las variables como argumentos en el mismo orden
ssh ${{ secrets.DEPLOY_USERNAME }}@${{ secrets.DEPLOY_HOST }} bash -s \
"${{ env.REGISTRY_URL }}" \
"${{ env.IMAGE_NAME }}" \
"${IMAGE_TAG}" \
"${{ gitea.sha }}" \
"${{ gitea.actor }}" \
"${{ gitea.run_id }}" \
"${{ secrets.TOKEN }}" << 'EOF'
set -euo pipefail
# 2. Las recibimos dentro de la sesión remota
REGISTRY_URL=$1
IMAGE_NAME=$2
IMAGE_TAG=$3
GIT_SHA=$4
GITEA_ACTOR=$5
BUILD_NUMBER=$6
TOKEN=$7
# Variables locales del script
GIT_BRANCH="staging"
BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Pulling image..."
echo "${{ secrets.TOKEN }}" | docker login ${{ env.REGISTRY_URL }} -u ${{ gitea.actor }} --password-stdin
docker pull ${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.image_tag }}
echo "$TOKEN" | docker login $REGISTRY_URL -u $GITEA_ACTOR --password-stdin
docker pull $REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
echo "Stopping existing container..."
docker stop cicd-staging 2>/dev/null || true
docker rm cicd-staging 2>/dev/null || true
echo "Starting new container..."
docker run -d \
--name cicd-staging \
--restart unless-stopped \
-p 8082:80 \
docker run -d --name cicd-staging --restart unless-stopped -p 8082:80 \
-e APP_ENV=staging \
-e APP_VERSION=staging-${{ gitea.sha }} \
-e GIT_COMMIT=${{ gitea.sha }} \
-e GIT_BRANCH=staging \
-e BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
-e DEPLOY_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
-e BUILD_NUMBER=${{ gitea.run_id }} \
${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:${{ needs.build-and-push.outputs.image_tag }}
echo "Running smoke tests..."
RESPONSE=$(curl -sf http://localhost:8082/health)
echo "Health response: $RESPONSE"
ENV_VALUE=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['env'])" 2>/dev/null || echo "unknown")
if [ "$ENV_VALUE" != "staging" ]; then
echo "::error::Smoke test failed: expected env=staging, got env=$ENV_VALUE"
-e APP_VERSION=staging-${GIT_SHA} \
-e GIT_COMMIT=${GIT_SHA} \
-e GIT_BRANCH=${GIT_BRANCH} \
-e BUILD_DATE=${BUILD_DATE} \
-e DEPLOY_TIME=${BUILD_DATE} \
-e BUILD_NUMBER=${BUILD_NUMBER} \
$REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
echo "Waiting for health check..."
HEALTHY=false
for i in $(seq 1 12); do
RESPONSE=$(curl -sf http://localhost:8082/health || echo "")
if [ -n "$RESPONSE" ]; then
ENV_VALUE=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['env'])" 2>/dev/null || echo "unknown")
if [ "$ENV_VALUE" = "staging" ]; then
echo "::notice::Staging smoke tests passed"
HEALTHY=true
break
fi
fi
sleep 5
done
if [ "$HEALTHY" = false ]; then
echo "::error::Staging smoke tests/health check failed"
exit 1
fi
echo "::notice::Staging smoke tests passed"
EOF
notify:
name: Notification
@@ -105,11 +136,11 @@ jobs:
steps:
- name: Write summary
run: |
cat << 'SUMMARY' >> $GITEA_HOME/workflow/summary
cat << 'SUMMARY' >> $GITHUB_STEP_SUMMARY
## Staging Deployment ${{ needs.deploy.result }}
**Branch:** staging
**Commit:** ${{ gitea.sha }}
**Image:** ${{ vars.REGISTRY_URL }}/${{ vars.IMAGE_NAME }}:staging-latest
**Image:** ${{ env.REGISTRY_URL }}/${{ env.IMAGE_NAME }}:staging-latest
**URL:** https://practicas.staging.kubistudio.cloud
SUMMARY
+37 -32
View File
@@ -2,7 +2,30 @@
Prueba de concepto de un pipeline CI/CD multi-ambiente con Gitea Actions, Docker y Nginx.
## Arquitectura
## 📌 Resumen de la Arquitectura
Implementación de una estrategia de Integración y Despliegue Continuo (CI/CD) para múltiples entornos (QA, Staging, Producción) alojada en un único servidor VPS (Debian/Ubuntu). La arquitectura utiliza **Gitea** (como control de versiones, Registry de contenedores y Runner de acciones), **Docker** para la contenerización de la aplicación estática, y un servidor **Nginx** en el host (Host Nginx) que actúa como Reverse Proxy y terminador SSL.
## 🏗️ Estrategia de Infraestructura y Red
Para aislar los entornos en el mismo servidor sin conflictos de red, se implementó el siguiente flujo:
* **Puertos Internos Dockerizados:** Cada entorno corre en un contenedor Docker independiente, exponiendo un puerto único a nivel de `localhost` en el VPS (ej. QA en `8081`, Staging en `8082`, Prod en `8083`).
* **Reverse Proxy:** El servidor Nginx principal del host escucha el puerto `80` y `443` (públicos). Mediante bloques `server_name`, intercepta las peticiones a subdominios específicos (`practicas.qa.kubistudio.cloud`, `practicas.staging.kubistudio.cloud`, `practicas.prod.kubistudio.cloud`) y hace un `proxy_pass` hacia el puerto interno correspondiente.
* **Terminación SSL:** La seguridad HTTPS se maneja exclusivamente en el Nginx del host utilizando **Certbot (Let's Encrypt)**. Los contenedores de Docker solo manejan tráfico HTTP interno, evitando colisiones en el puerto `443`.
## ⚙️ Inyección Dinámica de Variables (Frontend Estático)
Para cumplir con la premisa de *Build Once, Deploy Anywhere* en una aplicación estática (HTML/JS/CSS servida por Nginx), se solucionó el problema de las variables de entorno de la siguiente manera:
1. **Variables en el Pipeline:** El pipeline de Gitea Actions pasa variables de entorno (Commit SHA, Branch, App Env, Build Date) mediante la bandera `-e` en el comando `docker run`.
2. **Entrypoint Interceptor:** Se utiliza un script personalizado `docker-entrypoint.sh` en el contenedor.
3. **Generación Runtime:** Antes de que el Nginx del contenedor arranque, el script lee las variables de entorno de Linux y genera dinámicamente un archivo estático `/usr/share/nginx/html/env-config.js` inyectando el objeto `window.__ENV__`.
4. **Consumo en Cliente:** El archivo `index.html` carga este script de configuración antes de ejecutar la lógica de la aplicación, permitiendo que la interfaz cambie de aspecto y muestre la metadata correcta según el entorno (QA, Staging o Prod).
## 🚀 Flujo de las Pipelines de CD y Ramas
El despliegue está dividido en tres flujos principales basados en ramas:
```
feature/*
@@ -26,40 +49,22 @@ Prueba de concepto de un pipeline CI/CD multi-ambiente con Gitea Actions, Docker
└───────┘ (aprobación) (puerto 8083)
```
## Ambientes
### Detalle por Entorno
| Ambiente | URL | Puerto | Rama | Trigger |
|-----------|------------------------------------------|--------|---------|---------------|
| QA | https://practicas.qa.kubistudio.cloud | 8081 | `dev` | Push autom. |
| Staging | https://practicas.staging.kubistudio.cloud | 8082 | `staging` | Push autom. |
| Production| https://practicas.prod.kubistudio.cloud | 8083 | `main` | Push + aprob. |
* **QA (Entorno de Pruebas):**
* **Triggers:** Push a la rama `dev`.
* **Build & Push:** Construye la imagen Docker inyectando argumentos de compilación (`--build-arg` con el Git SHA y Build Date) y la sube al Gitea Container Registry con las etiquetas `qa-latest` y `sha-<hash>`.
* **Deploy (SSH):** Se conecta por SSH al VPS, detiene y elimina el contenedor anterior (`cicd-qa`), levanta el nuevo en el puerto `8081` con `APP_ENV=qa` y valida el despliegue con un health check.
## Configuración en Gitea
* **Staging (Entorno de Pre-producción):**
* **Triggers:** Push a la rama `staging`.
* **Build & Push:** Genera la imagen etiquetada para staging (`staging-latest`).
* **Deploy (SSH):** Detiene el contenedor anterior (`cicd-staging`), levanta el nuevo en el puerto `8082` con `APP_ENV=staging` y realiza smoke tests comprobando la variable de entorno expuesta en el de salud.
### Secretos (Settings > Secrets)
| Secreto | Descripción |
|--------------------|---------------------------------------------------|
| `TOKEN` | Token de Gitea con permisos de escritura al registry |
| `DEPLOY_SSH_KEY` | Clave SSH privada para acceder al servidor de deploy |
| `DEPLOY_USERNAME` | Usuario SSH para conexión al servidor |
| `DEPLOY_HOST` | Host/IP del servidor de deploy |
### Variables (Settings > Variables)
| Variable | Descripción | Ejemplo |
|------------------|------------------------------------------------------|------------------------------------------------|
| `REGISTRY_URL` | URL del Gitea Container Registry | `git.kubistudio.cloud` |
| `IMAGE_NAME` | Nombre de la imagen en el registry | `kubistudio/cicd-multi-env-pipeline-poc` |
## Flujo de Trabajo
1. Crear rama `feature/*` desde `dev`
2. Desarrollar y hacer commit
3. Abrir PR a `dev` → CI ejecuta lint, build, security scan
4. Merge a `dev` → CI + Deploy automático a QA
5. PR de `dev``staging` → Deploy automático a Staging
6. PR de `staging``main` → Requiere aprobación → Deploy a Producción
* **Production (Entorno de Producción):**
* **Triggers:** Push a la rama `main`.
* **Build & Push:** Genera la imagen inmutable de producción (`production-latest`, `stable` y `sha-<hash>`).
* **Deploy (SSH):** Guarda la imagen actual para rollback, levanta el nuevo contenedor en el puerto `8083` con `APP_ENV=production`. Si el health check falla, realiza un **rollback automático** restaurando la imagen anterior.
## Ejecución Local
+1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kubistudio - Multi-Environment Pipeline POC</title>
<script src="/env-config.js"></script>
<style>
:root {
--accent: #F59E0B;