This repository was archived by the owner on Apr 21, 2026. It is now read-only.
Sync docs from wheels@148ff79 #123
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy to Docker Swarm | |
| on: | |
| push: | |
| branches: | |
| - main | |
| env: | |
| IMAGE_NAME: ghcr.io/wheels-dev/wheels-dev | |
| REGISTRY: ghcr.io | |
| jobs: | |
| build: | |
| name: Build & Push Image | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Generate .env from secrets | |
| env: | |
| RELOAD_PASSWORD: ${{ secrets.RELOAD_PASSWORD }} | |
| CFCONFIG_ADMIN_PASSWORD: ${{ secrets.CFCONFIG_ADMIN_PASSWORD }} | |
| WHEELSDEV_HOST: ${{ secrets.WHEELSDEV_HOST }} | |
| WHEELSDEV_PORT: ${{ secrets.WHEELSDEV_PORT }} | |
| WHEELSDEV_DATABASENAME: ${{ secrets.WHEELSDEV_DATABASENAME }} | |
| WHEELSDEV_USERNAME: ${{ secrets.WHEELSDEV_USERNAME }} | |
| WHEELSDEV_PASSWORD: ${{ secrets.WHEELSDEV_PASSWORD }} | |
| SMTP_USERNAME: ${{ secrets.SMTP_USERNAME }} | |
| SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} | |
| WHEELS_ID_SALT: ${{ secrets.WHEELS_ID_SALT }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| run: | | |
| cat > .env << ENVEOF | |
| LUCEE_EXTENSIONS=BEC20D47-3268-1B354-C0E8E70B5CBC15A1;name=PostgreSQL;version=42.7.4 | |
| environment=production | |
| reloadPassword=${RELOAD_PASSWORD} | |
| cfconfig_adminPassword=${CFCONFIG_ADMIN_PASSWORD} | |
| application_host=https://wheels.dev | |
| datasource=wheels.dev | |
| wheelsdev_host=${WHEELSDEV_HOST} | |
| wheelsdev_port=${WHEELSDEV_PORT} | |
| wheelsdev_databasename=${WHEELSDEV_DATABASENAME} | |
| wheelsdev_username=${WHEELSDEV_USERNAME} | |
| wheelsdev_password=${WHEELSDEV_PASSWORD} | |
| wheelsdev_clob=true | |
| wheelsdev_connectionlimit=100 | |
| wheelsdev_storage=true | |
| wheelsdev_storage=true | |
| sessionStorage=wheels.dev | |
| sessionCluster=true | |
| test_case=false | |
| mail_from=noreply@wheels.dev | |
| smtp_host=smtp.postmarkapp.com | |
| smtp_port=587 | |
| smtp_username=${SMTP_USERNAME} | |
| smtp_password=${SMTP_PASSWORD} | |
| smtp_ssl=false | |
| smtp_tls=true | |
| wheels_id_salt=${WHEELS_ID_SALT} | |
| SENTRY_DSN=${SENTRY_DSN} | |
| SENTRY_ENVIRONMENT=production | |
| ENVEOF | |
| sed -i 's/^ //' .env | |
| - name: Copy swarm Dockerfile to root | |
| run: | | |
| cp ./deploy/swarm/dockerfile ./ | |
| - name: Install CommandBox | |
| uses: Ortus-Solutions/setup-commandbox@v2.0.1 | |
| with: | |
| forgeboxAPIKey: ${{ secrets.FORGEBOX_API_KEY }} | |
| - name: Install wheels-cli and dependencies | |
| run: | | |
| box install wheels-cli | |
| box install | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ./dockerfile | |
| push: true | |
| tags: | | |
| ${{ env.IMAGE_NAME }}:latest | |
| ${{ env.IMAGE_NAME }}:${{ github.sha }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| deploy: | |
| name: Deploy to Swarm | |
| runs-on: [self-hosted, Linux, swarm] | |
| needs: build | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Login to GHCR | |
| env: | |
| GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_ACTOR: ${{ github.actor }} | |
| run: | | |
| echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GH_ACTOR" --password-stdin | |
| - name: Set task history limit | |
| run: docker swarm update --task-history-limit 2 | |
| - name: Deploy stack via Portainer API | |
| env: | |
| PORTAINER_URL: ${{ secrets.PORTAINER_URL }} | |
| PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }} | |
| PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }} | |
| run: | | |
| COMPOSE_CONTENT=$(cat deploy/swarm/docker-compose.yml) | |
| # Get the swarm ID from Portainer | |
| SWARM_ID=$(curl -sfk \ | |
| -H "X-API-Key: ${PORTAINER_API_KEY}" \ | |
| "${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT_ID}/docker/swarm" \ | |
| | jq -r '.ID') | |
| echo "Swarm ID: ${SWARM_ID}" | |
| # Check if the stack already exists | |
| STACK_INFO=$(curl -sfk \ | |
| -H "X-API-Key: ${PORTAINER_API_KEY}" \ | |
| "${PORTAINER_URL}/api/stacks" \ | |
| | jq -r '.[] | select(.Name == "wheels-dev")') | |
| if [ -n "$STACK_INFO" ]; then | |
| STACK_ID=$(echo "$STACK_INFO" | jq -r '.Id') | |
| echo "Updating existing stack (ID: ${STACK_ID})..." | |
| HTTP_CODE=$(curl -sk -o /tmp/portainer-response.json -w "%{http_code}" \ | |
| -X PUT \ | |
| -H "X-API-Key: ${PORTAINER_API_KEY}" \ | |
| -H "Content-Type: application/json" \ | |
| "${PORTAINER_URL}/api/stacks/${STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}" \ | |
| -d "$(jq -n --arg content "$COMPOSE_CONTENT" '{ | |
| "stackFileContent": $content, | |
| "prune": true | |
| }')") | |
| else | |
| echo "Creating new stack..." | |
| HTTP_CODE=$(curl -sk -o /tmp/portainer-response.json -w "%{http_code}" \ | |
| -X POST \ | |
| -H "X-API-Key: ${PORTAINER_API_KEY}" \ | |
| -H "Content-Type: application/json" \ | |
| "${PORTAINER_URL}/api/stacks/create/swarm/string?endpointId=${PORTAINER_ENDPOINT_ID}" \ | |
| -d "$(jq -n --arg name "wheels-dev" --arg swarm "$SWARM_ID" --arg content "$COMPOSE_CONTENT" '{ | |
| "name": $name, | |
| "swarmID": $swarm, | |
| "stackFileContent": $content | |
| }')") | |
| fi | |
| echo "Portainer API response code: ${HTTP_CODE}" | |
| if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then | |
| echo "Error response:" | |
| cat /tmp/portainer-response.json | |
| exit 1 | |
| fi | |
| echo "Stack deployed successfully via Portainer" | |
| - name: Force pull latest image | |
| run: | | |
| for i in 1 2 3 4 5; do | |
| docker service update \ | |
| --with-registry-auth \ | |
| --image ghcr.io/wheels-dev/wheels-dev:latest \ | |
| --force \ | |
| wheels-dev_wheels-dev && break | |
| echo "Attempt $i failed (update out of sequence), retrying in 15s..." | |
| sleep 15 | |
| done | |
| - name: Verify deployment | |
| run: | | |
| echo "Waiting for services to stabilize..." | |
| sleep 30 | |
| docker service ls | |
| echo "" | |
| echo "Checking wheels-dev service replicas..." | |
| docker service ps wheels-dev_wheels-dev --no-trunc |