# Manual PROD deploy to Portainer. # # This does NOT build — build.yml already builds + pushes the api/web images. # This workflow (re)applies the deploy/*.stack.yml files to the Portainer Swarm. # Trigger it by hand from the Actions tab ("Run workflow") and choose: # - tag: which already-published image tag to ship (default: latest) # - scope: how much to deploy # app = web + api only (the usual app release) [default] # full = db + minio + web + api (bring up / update the whole platform) # # cssnr/portainer-stack-deploy-action creates each stack on first run and updates # it on every run, so no manual stack pre-creation in the Portainer UI. On a # `full` deploy the db + minio stacks are applied BEFORE the app (the API depends # on them). db + minio are stateful + pinned to node label jorgecuadros_db=true # (see their stack files) — re-applying them is idempotent and keeps their data. # # Prereqs (once): # - one swarm node labelled jorgecuadros_db=true (db + minio + api pin there). # - Gitea repo secrets set (Settings > Actions > Secrets): # # Portainer # PORTAINER_URL https://192.168.4.212:9443 # PORTAINER_API_KEY Portainer access token # PORTAINER_ENDPOINT_ID 2 (the local Swarm endpoint) # PORTAINER_APP_STACK_NAME e.g. jorgecuadros-prod-app # PORTAINER_DB_STACK_NAME e.g. jorgecuadros-prod-db (full only) # PORTAINER_MINIO_STACK_NAME e.g. jorgecuadros-prod-minio (full only) # # App runtime # DATABASE_URL mysql://jorgecuadros:@192.168.4.212:3306/jorgecuadros # SESSION_SECRET 64-hex (openssl rand -hex 32) # APP_API_ORIGIN http://192.168.4.212:3001 (browser-facing API URL) # APP_WEB_ORIGIN http://192.168.4.212:3000 (web public origin, API CORS) # APP_S3_ENDPOINT http://192.168.4.212:9000 (server-side minio URL) # # Object storage (app + minio stack) # MINIO_ROOT_USER minio access key # MINIO_ROOT_PASSWORD minio secret key # # Database stack (full only) # MYSQL_PASSWORD app-user password (matches DATABASE_URL) # MYSQL_ROOT_PASSWORD mysql root password name: Deploy to Portainer on: workflow_dispatch: inputs: tag: description: "Image tag to deploy (latest, sha-, or vX.Y.Z)" required: true default: "latest" scope: description: "What to deploy" type: choice required: true default: "app" options: - app - full env: REGISTRY: git.mancinas.io jobs: deploy: name: Deploy (${{ github.event.inputs.scope }}) runs-on: docker container: image: node:18-alpine steps: - uses: actions/checkout@v4 # --- full only: database --------------------------------------------- - name: Deploy database stack if: ${{ github.event.inputs.scope == 'full' }} uses: cssnr/portainer-stack-deploy-action@v1 with: url: ${{ secrets.PORTAINER_URL }} token: ${{ secrets.PORTAINER_API_KEY }} name: ${{ secrets.PORTAINER_DB_STACK_NAME }} file: deploy/jorgecuadros-db.stack.yml type: file endpoint_id: ${{ secrets.PORTAINER_ENDPOINT_ID }} env_data: | { "MYSQL_SERVER_ID": "1", "MYSQL_PORT": "3306", "MYSQL_DATABASE": "jorgecuadros", "MYSQL_USER": "jorgecuadros", "MYSQL_PASSWORD": "${{ secrets.MYSQL_PASSWORD }}", "MYSQL_ROOT_PASSWORD": "${{ secrets.MYSQL_ROOT_PASSWORD }}" } # --- full only: object storage --------------------------------------- - name: Deploy minio stack if: ${{ github.event.inputs.scope == 'full' }} uses: cssnr/portainer-stack-deploy-action@v1 with: url: ${{ secrets.PORTAINER_URL }} token: ${{ secrets.PORTAINER_API_KEY }} name: ${{ secrets.PORTAINER_MINIO_STACK_NAME }} file: deploy/jorgecuadros-minio.stack.yml type: file endpoint_id: ${{ secrets.PORTAINER_ENDPOINT_ID }} env_data: | { "MINIO_API_PORT": "9000", "MINIO_CONSOLE_PORT": "9001", "MINIO_ROOT_USER": "${{ secrets.MINIO_ROOT_USER }}", "MINIO_ROOT_PASSWORD": "${{ secrets.MINIO_ROOT_PASSWORD }}" } # --- always: the app (web + api) ------------------------------------- - name: Deploy app stack uses: cssnr/portainer-stack-deploy-action@v1 with: url: ${{ secrets.PORTAINER_URL }} token: ${{ secrets.PORTAINER_API_KEY }} name: ${{ secrets.PORTAINER_APP_STACK_NAME }} file: deploy/jorgecuadros-app.stack.yml type: file pull_image: true endpoint_id: ${{ secrets.PORTAINER_ENDPOINT_ID }} env_data: | { "APP_TAG": "${{ github.event.inputs.tag }}", "API_PORT": "3001", "WEB_PORT": "3000", "S3_BUCKET": "jorgecuadros-documents", "API_ORIGIN": "${{ secrets.APP_API_ORIGIN }}", "WEB_ORIGIN": "${{ secrets.APP_WEB_ORIGIN }}", "S3_ENDPOINT": "${{ secrets.APP_S3_ENDPOINT }}", "DATABASE_URL": "${{ secrets.DATABASE_URL }}", "SESSION_SECRET": "${{ secrets.SESSION_SECRET }}", "MINIO_ROOT_USER": "${{ secrets.MINIO_ROOT_USER }}", "MINIO_ROOT_PASSWORD": "${{ secrets.MINIO_ROOT_PASSWORD }}" }