From b129cd4f0c6bd2967c443f7fa61004b757c318eb Mon Sep 17 00:00:00 2001 From: Francis Secada Date: Mon, 2 Feb 2026 16:30:10 -0500 Subject: [PATCH] feat: modernize deployment pipeline for Komodo integration Production Deployment Improvements: - Added .env.example template for secure credential management - Modernized Docker build workflow with branch sanitization - Created Komodo deployment trigger workflow - Updated compose.yaml with environment variable substitution GitHub Workflows: - Updated docker-image.yml: - Add branch name sanitization (replace / with -) - Generate both date-tagged and -latest Docker tags - Upgrade to actions/checkout@v4 - Add pull: true for layer caching - New komodo-deploy.yml: - Triggers after successful Docker Image CI - Sends signed webhook to Komodo service - Extracts and sanitizes branch names - Requires secrets: KOMODO_HOST, KOMODO_STACK_ID_OR_NAME, KOMODO_WEBHOOK_SECRET Docker Compose Modernization: - Dynamic IMAGE_TAG with sensible defaults (main-latest) - Configurable memory limits and reservations - Environment variable substitution for all configs - Added container names and restart policies - Improved healthcheck with Host header - Updated Traefik labels with Let's Encrypt cert resolver - Added loadbalancer server URL configuration - Explicit command paths for reliability Security: - .env.example provides safe template (no credentials) - Actual .env remains in .gitignore (not tracked) Pattern based on proven Formana deployment architecture. Co-Authored-By: Claude Sonnet 4.5 --- .env.example | 40 +++++++++++++++++++++ .github/workflows/docker-image.yml | 13 +++++-- .github/workflows/komodo-deploy.yml | 42 ++++++++++++++++++++++ compose.yaml | 56 +++++++++++++++++------------ 4 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/komodo-deploy.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..531f4f7 --- /dev/null +++ b/.env.example @@ -0,0 +1,40 @@ +# OpenAI Configuration +OPENAI_API_KEY=sk-proj-your-openai-api-key-here +OPENAI_MODEL=gpt-4o-mini +# OPENAI_MODEL=gpt-4o + +# Security +HTTPS_ONLY=False +SECRET_KEY=your-secret-key-here + +# Local Database Configuration +LOCAL_DB_UN=pygentic_ai +LOCAL_DB_PW=your-local-db-password +LOCAL_DB_DB=fsecada_server +LOCAL_DB_HOST=localhost +LOCAL_DB_PORT=5433 + +# Cloud Database Configuration +CLOUD_DB_UN=pygentic_ai +CLOUD_DB_PW=your-cloud-db-password +CLOUD_DB_DB=fsecada_server +CLOUD_DB_HOST=pgserver.example.com +CLOUD_DB_PORT=5433 + +# Database Configuration +SQL_DIALECT=postgresql + +# Application Environment +DEBUG=True +SERVER_ENV=dev + +# Reddit API Configuration (for intelligence gathering) +REDDIT_MAX_INSIGHTS=5 +REDDIT_MAX_INSIGHT_LENGTH=400 +REDDIT_USER_AGENT=your-reddit-user-agent +REDDIT_CLIENT_ID=your-reddit-client-id +REDDIT_CLIENT_SECRET=your-reddit-client-secret +REDDIT_SUBREDDIT=python,kubernetes,devops,rust,selfhosted,algorithms,typescript,programming,webdev,strategy,DigitalMarketing,Entrepreneur + +# Celery Configuration (optional) +# CELERY_PORT=5052 diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 057d4d7..e6cf43c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -24,13 +24,17 @@ jobs: shell: bash run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT id: extract_branch + - name: Sanitize branch name for Docker tag + shell: bash + run: echo "sanitized_branch=$(echo '${{ steps.extract_branch.outputs.branch }}' | sed 's/\//-/g')" >> $GITHUB_OUTPUT + id: sanitize_branch - name: Set SSH Agent uses: webfactory/ssh-agent@v0.9.0 with: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: @@ -41,6 +45,9 @@ jobs: ssh: | default=${{ env.SSH_AUTH_SOCK }} build-args: | - GIT_BRANCH=${{ steps.extract_branch.outputs.branch }} + "GIT_BRANCH=${{ steps.extract_branch.outputs.branch }}" push: true - tags: s3docker.francissecada.com/pygentic_ai:${{ steps.extract_branch.outputs.branch }}.${{ steps.date.outputs.date }} + tags: | + s3docker.francissecada.com/pygentic_ai:${{ steps.sanitize_branch.outputs.sanitized_branch }}.${{ steps.date.outputs.date }} + s3docker.francissecada.com/pygentic_ai:${{ steps.sanitize_branch.outputs.sanitized_branch }}-latest + pull: true diff --git a/.github/workflows/komodo-deploy.yml b/.github/workflows/komodo-deploy.yml new file mode 100644 index 0000000..623b59e --- /dev/null +++ b/.github/workflows/komodo-deploy.yml @@ -0,0 +1,42 @@ +name: Komodo Deployment Trigger + +on: + workflow_run: + workflows: ["Docker Image CI"] + types: + - completed + branches: [ "**_deploy", "main" ] + +jobs: + trigger_komodo_deployment: + runs-on: ubuntu-latest + # Only run if the 'Docker Image CI' workflow was successful + if: github.event.workflow_run.conclusion == 'success' + steps: + - name: Extract branch name from triggering workflow + id: branch + run: echo "BRANCH_NAME=$(echo ${{ github.event.workflow_run.head_branch }} | sed 's/\//-/g')" >> $GITHUB_OUTPUT + + - name: Trigger Komodo Stack Deployment + env: + # The webhook URL is constructed from secrets + KOMODO_WEBHOOK_URL: "https://${{ secrets.KOMODO_HOST }}/listener/github/stack/${{ secrets.KOMODO_STACK_ID_OR_NAME }}/deploy" + run: | + # Get the branch name from the previous step's output + branch_name="${{ steps.branch.outputs.BRANCH_NAME }}" + + # Construct a JSON payload containing the branch name + # This tells Komodo which specific branch/tag to deploy + payload=$(printf '{"ref":"%s"}' "$branch_name") + + # Generate the signature based on the payload + signature=$(echo -n "$payload" | openssl dgst -sha256 -hmac "${{ secrets.KOMODO_WEBHOOK_SECRET }}" | sed 's/^.* //') + + # Send the request to the Komodo webhook with the payload + echo "Triggering Pygentic-AI deployment for branch: $branch_name" + curl -X POST \ + -H "Content-Type: application/json" \ + -H "X-Hub-Signature-256: sha256=$signature" \ + -d "$payload" \ + --fail-with-body \ + "${KOMODO_WEBHOOK_URL}" diff --git a/compose.yaml b/compose.yaml index 496fce7..2a81298 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,58 +1,68 @@ services: web: - image: s3docker.francissecada.com/pygentic_ai:main.2024-11-30 + image: s3docker.francissecada.com/pygentic_ai:${IMAGE_TAG:-main-latest} + container_name: pygentic_ai + restart: unless-stopped deploy: resources: limits: - memory: 1024mb + memory: ${MEMORY_LIMIT:-1024mb} + reservations: + memory: ${MEMORY_RESERVATION:-512mb} ports: - - "5051:5051" + - "0.0.0.0:${PORT:-5051}:${INTERNAL_PORT:-5051}" env_file: - ./stack.env environment: - PORT: 5051 - SERVER_ENV: prod + - PORT=${INTERNAL_PORT:-5051} + - SERVER_ENV=${SERVER_ENV:-prod} volumes: - ./src/proxy_urls.db:/opt/pygentic_ai/src/proxy_urls.db + command: bash -c "/opt/pygentic_ai/docker/pygentic_ai/python_start.sh" + healthcheck: + test: ["CMD", "curl", "-f", "-H", "Host: pygenticai.francissecada.com", "http://localhost:${INTERNAL_PORT:-5051}/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s labels: - traefik.enable=true - - traefik.docker.network=proxy - - traefik.http.routers.pygentic_ai.entrypoints=websecure - traefik.http.routers.pygentic_ai.rule=Host(`pygenticai.francissecada.com`) - - traefik.http.routers.pygentic_ai.tls=true - healthcheck: - test: curl --fail http://localhost:5051/ || exit 1 - interval: 40s - timeout: 30s - retries: 3 - start_period: 60s + - traefik.http.routers.pygentic_ai.entrypoints=websecure + - traefik.http.routers.pygentic_ai.tls.certresolver=letsencrypt + - traefik.http.services.pygentic_ai.loadbalancer.server.url=http://${DOCKER_HOST_IP:-192.168.99.85}:${PORT:-5051} networks: - proxy + celery_service: - image: s3docker.francissecada.com/pygentic_ai:main.2024-11-30 + image: s3docker.francissecada.com/pygentic_ai:${IMAGE_TAG:-main-latest} + container_name: pygentic_ai_celery + restart: unless-stopped deploy: resources: limits: - memory: 512mb - # build: . - command: ./docker/celery/start.sh + memory: ${CELERY_MEMORY_LIMIT:-512mb} + reservations: + memory: ${CELERY_MEMORY_RESERVATION:-256mb} + command: bash -c "/opt/pygentic_ai/docker/celery/start.sh" env_file: - ./stack.env environment: - PORT: ${CELERY_PORT} - SERVER_ENV: staging - C_FORCE_ROOT: true + - PORT=${CELERY_PORT:-5052} + - SERVER_ENV=${SERVER_ENV:-prod} + - C_FORCE_ROOT=true ports: - - "5052:5052" + - "0.0.0.0:${CELERY_PORT:-5052}:${CELERY_PORT:-5052}" labels: - traefik.enable=true - - traefik.docker.network=proxy - traefik.http.routers.celery_pygentic_ai.entrypoints=websecure - traefik.http.routers.celery_pygentic_ai.rule=Host(`celery.pygenticai.francissecada.com`) + - traefik.http.routers.celery_pygentic_ai.tls.certresolver=letsencrypt networks: - proxy depends_on: - web + networks: proxy: external: true