Revert to "Finalized Workflow to upload new docker image on push"
All checks were successful
Docker Build, Push, and Deploy to Portainer / Build and Push Docker Image (push) Successful in 1m43s
All checks were successful
Docker Build, Push, and Deploy to Portainer / Build and Push Docker Image (push) Successful in 1m43s
This commit is contained in:
@ -40,151 +40,3 @@ jobs:
|
|||||||
- name: Logout from Gitea Package Regsitry
|
- name: Logout from Gitea Package Regsitry
|
||||||
if: always()
|
if: always()
|
||||||
run: docker logout ${{ vars.REGISTRY_URL }}
|
run: docker logout ${{ vars.REGISTRY_URL }}
|
||||||
|
|
||||||
deploy-to-portainer:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Deploy Application to Portainer
|
|
||||||
needs: build-and-push
|
|
||||||
|
|
||||||
env:
|
|
||||||
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
|
|
||||||
PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }}
|
|
||||||
PORTAINER_ENVIRONMENT_ID: ${{ secrets.PORTAINER_ENVIRONMENT_ID }}
|
|
||||||
IMAGE_TO_DEPLOY: ${{ vars.REGISTRY_URL }}/${{ gitea.repository }}:latest
|
|
||||||
IMAGE_NAME: ${{ gitea.repository }}:latest
|
|
||||||
CONTAINER_NAME: ${{ vars.PORTAINER_CONTAINER_NAME || 'deep-research' }} # Name of the container in Portainer
|
|
||||||
HOST_PORT: "8005" # Host port to map to container's 3000
|
|
||||||
CONTAINER_PORT: "3000" # Container port
|
|
||||||
RESTART_POLICY: "unless-stopped"
|
|
||||||
# For curl: -k allows insecure connections (e.g. self-signed certs). Omit for production with valid certs.
|
|
||||||
# CURL_OPTS: "-s -k" # If you need -k
|
|
||||||
CURL_OPTS: "-s" # Silent mode
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Deploy Container to Portainer via API
|
|
||||||
run: |
|
|
||||||
set -e # Exit immediately if a command exits with a non-zero status.
|
|
||||||
echo "Starting deployment of image ${{ env.IMAGE_TO_DEPLOY }} as container ${{ env.CONTAINER_NAME }}"
|
|
||||||
|
|
||||||
PORTAINER_API_BASE="${{ env.PORTAINER_URL }}/api/endpoints/${{ env.PORTAINER_ENVIRONMENT_ID }}"
|
|
||||||
PORTAINER_API_DOCKER_BASE="${PORTAINER_API_BASE}/docker"
|
|
||||||
AUTH_HEADER="X-API-Key: ${{ env.PORTAINER_API_KEY }}"
|
|
||||||
|
|
||||||
# 0. Explicitly pull the image using Portainer's configured registry credentials
|
|
||||||
echo "Attempting to pull image '${{ env.IMAGE_TO_DEPLOY }}' via Portainer..."
|
|
||||||
IMAGE_FULL_NAME_FOR_PULL="${{ env.IMAGE_TO_DEPLOY }}"
|
|
||||||
# Extract image name (everything before the last colon) and tag (everything after the last colon)
|
|
||||||
IMAGE_NAME_NO_TAG_FOR_PULL=$(echo "$IMAGE_FULL_NAME_FOR_PULL" | sed -n 's/\(.*\):\([^:]*\)/\1/p')
|
|
||||||
IMAGE_TAG_FOR_PULL=$(echo "$IMAGE_FULL_NAME_FOR_PULL" | sed -n 's/\(.*\):\([^:]*\)/\2/p')
|
|
||||||
|
|
||||||
# If sed didn't find a tag (e.g., image name had no colon, or only one part like 'imagename'), assume 'latest'
|
|
||||||
if [ -z "$IMAGE_TAG_FOR_PULL" ] || [ "$IMAGE_NAME_NO_TAG_FOR_PULL" == "" ]; then
|
|
||||||
IMAGE_TAG_FOR_PULL="latest"
|
|
||||||
IMAGE_NAME_NO_TAG_FOR_PULL="$IMAGE_FULL_NAME_FOR_PULL" # Assume full name was without tag if parsing failed
|
|
||||||
if echo "$IMAGE_NAME_NO_TAG_FOR_PULL" | grep -q ':'; then # If it still contains ':', it's likely it was just 'image:tag' and parsing failed above
|
|
||||||
IMAGE_NAME_NO_TAG_FOR_PULL=$(echo "$IMAGE_FULL_NAME_FOR_PULL" | cut -d: -f1) # fallback for simple 'image:tag'
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Pulling image name: '$IMAGE_NAME_NO_TAG_FOR_PULL' with tag: '$IMAGE_TAG_FOR_PULL'"
|
|
||||||
|
|
||||||
HTTP_CODE_PULL=$(curl ${{ env.CURL_OPTS }} -w "%{http_code}" -o /tmp/portainer_pull_response.txt -X POST \
|
|
||||||
-H "$AUTH_HEADER" \
|
|
||||||
"${PORTAINER_API_BASE}/images/create?fromImage=${IMAGE_NAME_NO_TAG_FOR_PULL}&tag=${IMAGE_TAG_FOR_PULL}")
|
|
||||||
PULL_RESPONSE_BODY=$(cat /tmp/portainer_pull_response.txt)
|
|
||||||
|
|
||||||
if [ "$HTTP_CODE_PULL" -eq 200 ]; then
|
|
||||||
echo "Image pull initiated successfully by Portainer (HTTP 200)."
|
|
||||||
echo "Portainer pull response (first 10 lines):"; head -n 10 /tmp/portainer_pull_response.txt
|
|
||||||
echo "Waiting 60 seconds for image pull to complete..."
|
|
||||||
sleep 60
|
|
||||||
elif [ "$HTTP_CODE_PULL" -eq 401 ] || [ "$HTTP_CODE_PULL" -eq 403 ] || [ "$HTTP_CODE_PULL" -eq 500 ]; then
|
|
||||||
echo "Error during image pull (HTTP $HTTP_CODE_PULL). This strongly indicates an AUTHENTICATION FAILURE with the Gitea registry via Portainer."
|
|
||||||
echo "Please RE-VERIFY the credentials (username/token and its permissions) for '${{ vars.REGISTRY_URL }}' in Portainer's Registries section."
|
|
||||||
echo "Response body: $PULL_RESPONSE_BODY"; exit 1
|
|
||||||
elif [ "$HTTP_CODE_PULL" -eq 404 ]; then
|
|
||||||
echo "Error during image pull (HTTP $HTTP_CODE_PULL). Image '${IMAGE_NAME_NO_TAG_FOR_PULL}:${IMAGE_TAG_FOR_PULL}' not found in the Gitea registry."; echo "Response body: $PULL_RESPONSE_BODY"; exit 1
|
|
||||||
else
|
|
||||||
echo "Error initiating image pull. Portainer responded with HTTP code: $HTTP_CODE_PULL"; echo "Response body: $PULL_RESPONSE_BODY"; exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 1. Find existing container by name
|
|
||||||
echo "Searching for existing container named '${{ env.CONTAINER_NAME }}'..."
|
|
||||||
# Portainer API often prepends '/' to container names in the .Names field.
|
|
||||||
# Using jq to filter based on any name in the .Names array matching /<CONTAINER_NAME>
|
|
||||||
EXISTING_CONTAINER_ID=$(curl ${{ env.CURL_OPTS }} -H "$AUTH_HEADER" \
|
|
||||||
"${PORTAINER_API_DOCKER_BASE}/containers/json?all=true" | \
|
|
||||||
jq -r --arg CN "/${{ env.CONTAINER_NAME }}" '.[] | select(.Names[] | contains($CN)) | .Id' | head -n 1)
|
|
||||||
|
|
||||||
# 2. If container exists, stop and remove it
|
|
||||||
if [ -n "$EXISTING_CONTAINER_ID" ]; then
|
|
||||||
echo "Found existing container '${{ env.CONTAINER_NAME }}' with ID '$EXISTING_CONTAINER_ID'."
|
|
||||||
echo "Stopping container..."
|
|
||||||
curl ${{ env.CURL_OPTS }} -X POST -H "$AUTH_HEADER" "${PORTAINER_API_DOCKER_BASE}/containers/${EXISTING_CONTAINER_ID}/stop"
|
|
||||||
echo "Waiting for container to stop..."
|
|
||||||
sleep 5 # Adjust sleep time as needed
|
|
||||||
echo "Removing container..."
|
|
||||||
curl ${{ env.CURL_OPTS }} -X DELETE -H "$AUTH_HEADER" "${PORTAINER_API_DOCKER_BASE}/containers/${EXISTING_CONTAINER_ID}"
|
|
||||||
echo "Container '${{ env.CONTAINER_NAME }}' removed."
|
|
||||||
else
|
|
||||||
echo "No existing container named '${{ env.CONTAINER_NAME }}' found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3. Create new container
|
|
||||||
echo "Creating new container '${{ env.CONTAINER_NAME }}' with image '${{ env.IMAGE_TO_DEPLOY }}'..."
|
|
||||||
CREATE_PAYLOAD=$(cat <<EOF
|
|
||||||
{
|
|
||||||
"Image": "${{ env.IMAGE_NAME }}",
|
|
||||||
"ExposedPorts": {
|
|
||||||
"${{ env.CONTAINER_PORT }}/tcp": {}
|
|
||||||
},
|
|
||||||
"HostConfig": {
|
|
||||||
"PortBindings": {
|
|
||||||
"${{ env.CONTAINER_PORT }}/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "${{ env.HOST_PORT }}" } ]
|
|
||||||
},
|
|
||||||
"RestartPolicy": { "Name": "${{ env.RESTART_POLICY }}", "MaximumRetryCount": 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
CREATE_RESPONSE=$(curl ${{ env.CURL_OPTS }} -X POST -H "$AUTH_HEADER" -H "Content-Type: application/json" \
|
|
||||||
--data "$CREATE_PAYLOAD" \
|
|
||||||
"${PORTAINER_API_DOCKER_BASE}/containers/create?name=${{ env.CONTAINER_NAME }}")
|
|
||||||
|
|
||||||
NEW_CONTAINER_ID=$(echo "$CREATE_RESPONSE" | jq -r '.Id')
|
|
||||||
WARNINGS=$(echo "$CREATE_RESPONSE" | jq -r '.Warnings // empty') # Handle null warnings
|
|
||||||
|
|
||||||
if [ -z "$NEW_CONTAINER_ID" ] || [ "$NEW_CONTAINER_ID" == "null" ]; then
|
|
||||||
echo "Failed to create container '${{ env.CONTAINER_NAME }}'."
|
|
||||||
echo "Response from Portainer API: $CREATE_RESPONSE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Container '${{ env.CONTAINER_NAME }}' created successfully with ID '$NEW_CONTAINER_ID'."
|
|
||||||
if [ -n "$WARNINGS" ]; then
|
|
||||||
echo "Warnings from Portainer during creation: $WARNINGS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 4. Start the new container
|
|
||||||
echo "Starting container '${{ env.CONTAINER_NAME }}' (ID: $NEW_CONTAINER_ID)..."
|
|
||||||
# We capture HTTP code and response body separately for better error diagnosis.
|
|
||||||
HTTP_CODE_START=$(curl ${{ env.CURL_OPTS }} -w "%{http_code}" -o /tmp/portainer_start_response.txt -X POST \
|
|
||||||
-H "$AUTH_HEADER" \
|
|
||||||
"${PORTAINER_API_DOCKER_BASE}/containers/${NEW_CONTAINER_ID}/start")
|
|
||||||
START_RESPONSE_BODY=$(cat /tmp/portainer_start_response.txt)
|
|
||||||
|
|
||||||
if [ "$HTTP_CODE_START" -eq 204 ]; then
|
|
||||||
echo "Container '${{ env.CONTAINER_NAME }}' started successfully."
|
|
||||||
elif [ "$HTTP_CODE_START" -eq 304 ]; then # Not Modified - already started
|
|
||||||
echo "Container '${{ env.CONTAINER_NAME }}' was already started."
|
|
||||||
else
|
|
||||||
echo "Failed to start container '${{ env.CONTAINER_NAME }}'. Portainer responded with HTTP code: $HTTP_CODE_START"
|
|
||||||
echo "Response body: $START_RESPONSE_BODY"
|
|
||||||
# Fetch logs for the container to help diagnose start issues
|
|
||||||
sleep 2
|
|
||||||
echo "Fetching last 20 lines of logs for container '$NEW_CONTAINER_ID':"
|
|
||||||
curl ${{ env.CURL_OPTS }} -H "$AUTH_HEADER" \
|
|
||||||
"${PORTAINER_API_DOCKER_BASE}/containers/${NEW_CONTAINER_ID}/logs?stdout=true&stderr=true&tail=20×tamps=true"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Deployment of '${{ env.CONTAINER_NAME }}' completed."
|
|
||||||
|
Reference in New Issue
Block a user