Skip to content
This repository was archived by the owner on Apr 21, 2026. It is now read-only.

Sync docs from wheels@148ff79 #123

Sync docs from wheels@148ff79

Sync docs from wheels@148ff79 #123

Workflow file for this run

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