Table of Contents
GitLab CI PR Environment Automation
This guide shows how to set up GitLab CI to automatically create and update ephemeral environments for your pull requests using Blueberry IDP.
Overview
The Blueberry IDP webhook system provides a seamless way to:
- Create environments automatically when a PR is opened
- Update environments when new commits are pushed to the PR branch
- Single pod deployment with acceptable downtime during updates
- Manual redeploy option for troubleshooting
Prerequisites
- API Token: Create an API token in the Blueberry IDP with
environments:create
scope - Repository Configuration: Your repository must be configured in the Blueberry IDP
- Docker Registry Access: Your CI must push images to the configured registry
GitLab CI Configuration
1. Set up CI/CD Variables
In your GitLab project, go to Settings > CI/CD > Variables and add:
# Required
BLUEBERRY_API_URL=https://blueberry.florenciacomuzzi.com
BLUEBERRY_API_TOKEN=your_api_token_here
# Optional (if using custom registry)
DOCKER_REGISTRY=us-docker.pkg.dev/development-454916/blueberry
2. Basic .gitlab-ci.yml
Example
# .gitlab-ci.yml
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_NAME: "$DOCKER_REGISTRY/$CI_PROJECT_NAME"
IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
stages:
- test
- build
- deploy-environment
# Run tests
test:
stage: test
image: python:3.11-slim
script:
- pip install -r requirements.txt
- pytest tests/
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Build and push Docker image
build:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
before_script:
- echo $GCLOUD_SERVICE_KEY | docker login -u _json_key --password-stdin https://us-docker.pkg.dev
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker push $IMAGE_NAME:$IMAGE_TAG
- echo "Built and pushed $IMAGE_NAME:$IMAGE_TAG"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Create or update PR environment
deploy-pr-environment:
stage: deploy-environment
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
# Determine if this is a merge request
if [ "$CI_PIPELINE_SOURCE" = "merge_request_event" ]; then
echo "Deploying PR environment for MR !$CI_MERGE_REQUEST_IID"
# Call Blueberry webhook to create/update environment
RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST \
"$BLUEBERRY_API_URL/api/webhooks/pr-environment" \
-H "Authorization: Bearer $BLUEBERRY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo_url": "'$CI_PROJECT_URL'",
"repo_name": "'$CI_PROJECT_NAME'",
"pr_number": '$CI_MERGE_REQUEST_IID',
"branch": "'$CI_COMMIT_REF_NAME'",
"target_branch": "'$CI_MERGE_REQUEST_TARGET_BRANCH_NAME'",
"commit_sha": "'$CI_COMMIT_SHA'",
"commit_message": "'$CI_COMMIT_MESSAGE'",
"commit_author": "'$CI_COMMIT_AUTHOR'",
"ttl_hours": 168
}')
# Extract HTTP status and response body
HTTP_BODY=$(echo $RESPONSE | sed -E 's/HTTPSTATUS\:[0-9]{3}$//')
HTTP_STATUS=$(echo $RESPONSE | tr -d '\n' | sed -E 's/.*HTTPSTATUS:([0-9]{3})$/\1/')
echo "HTTP Status: $HTTP_STATUS"
echo "Response: $HTTP_BODY"
# Check if request was successful
if [ "$HTTP_STATUS" -eq 200 ]; then
echo "â
Environment deployment successful!"
# Extract URLs from response if available
APP_URL=$(echo "$HTTP_BODY" | jq -r '.urls.app // empty')
if [ -n "$APP_URL" ] && [ "$APP_URL" != "null" ]; then
echo "đ Environment URL: $APP_URL"
# Add comment to merge request (optional)
if command -v gitlab-comment >/dev/null 2>&1; then
gitlab-comment "đ PR Environment deployed: [$APP_URL](/developer-docs/docs/setup/05-cicd/$APP_URL)"
fi
fi
# Extract environment name and action
ENV_NAME=$(echo "$HTTP_BODY" | jq -r '.environment_name // empty')
ACTION=$(echo "$HTTP_BODY" | jq -r '.action // empty')
if [ -n "$ENV_NAME" ]; then
echo "đ Environment: $ENV_NAME (action: $ACTION)"
fi
else
echo "â Environment deployment failed!"
echo "Error details: $HTTP_BODY"
exit 1
fi
else
echo "âšī¸ Not a merge request, skipping PR environment deployment"
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
dependencies:
- build
3. Advanced Configuration with Frontend Apps
For applications with multiple frontend components:
deploy-pr-environment-full:
stage: deploy-environment
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
if [ "$CI_PIPELINE_SOURCE" = "merge_request_event" ]; then
echo "Deploying full-stack PR environment for MR !$CI_MERGE_REQUEST_IID"
# Call Blueberry webhook with frontend apps
curl -X POST "$BLUEBERRY_API_URL/api/webhooks/pr-environment" \
-H "Authorization: Bearer $BLUEBERRY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo_url": "'$CI_PROJECT_URL'",
"repo_name": "'$CI_PROJECT_NAME'",
"pr_number": '$CI_MERGE_REQUEST_IID',
"branch": "'$CI_COMMIT_REF_NAME'",
"target_branch": "'$CI_MERGE_REQUEST_TARGET_BRANCH_NAME'",
"commit_sha": "'$CI_COMMIT_SHA'",
"commit_message": "'$CI_COMMIT_MESSAGE'",
"commit_author": "'$CI_COMMIT_AUTHOR'",
"ttl_hours": 168,
"frontend_apps": [
{
"repo_id": "frontend-app",
"commit_sha": "'$FRONTEND_COMMIT_SHA'",
"branch": "main"
}
]
}'
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
FRONTEND_COMMIT_SHA: "latest" # Or fetch from another repo
4. Simplified Version
For basic use cases, use the simplified endpoint:
deploy-simple:
stage: deploy-environment
image: alpine:latest
before_script:
- apk add --no-cache curl jq
script:
- |
if [ "$CI_PIPELINE_SOURCE" = "merge_request_event" ]; then
curl -X POST "$BLUEBERRY_API_URL/api/webhooks/pr-environment/simple" \
-H "Authorization: Bearer $BLUEBERRY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo_name": "'$CI_PROJECT_NAME'",
"pr_number": '$CI_MERGE_REQUEST_IID',
"branch": "'$CI_COMMIT_REF_NAME'",
"commit_sha": "'$CI_COMMIT_SHA'"
}'
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Environment Lifecycle
Automatic Creation
When you open a merge request, the CI pipeline will:
1. Run tests
2. Build Docker image
3. Call Blueberry webhook to create environment
Automatic Updates
When you push new commits to the MR branch:
1. Build new Docker image with updated commit SHA
2. Call Blueberry webhook to update existing environment
3. Environment is updated with new image (downtime acceptable)
Manual Actions
In the Blueberry IDP environment detail page, you can:
- Redeploy: Restart the environment with current configuration
- Extend TTL: Keep the environment alive longer
- Delete: Clean up the environment manually
Environment Naming
The system uses predictable naming:
- PR-based: pr-123
(for MR #123)
- Branch-based: backend-service-feature-branch-abcd1234
Troubleshooting
Common Issues
-
Image not found: Ensure Docker image is built and pushed before calling webhook
bash # Check if image exists docker manifest inspect $IMAGE_NAME:$IMAGE_TAG
-
Authentication failed: Verify your API token has correct scopes
bash curl -H "Authorization: Bearer $BLUEBERRY_API_TOKEN" \ "$BLUEBERRY_API_URL/api/health"
-
Environment not updating: Check if environment exists and is not terminated
- Environment might have been manually deleted
- TTL might have expired
Debugging Commands
# Test webhook manually
curl -X POST "$BLUEBERRY_API_URL/api/webhooks/pr-environment/simple" \
-H "Authorization: Bearer $BLUEBERRY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo_name": "your-repo",
"pr_number": 123,
"branch": "feature-branch",
"commit_sha": "abcd1234"
}'
# Check environment status
curl -H "Authorization: Bearer $BLUEBERRY_API_TOKEN" \
"$BLUEBERRY_API_URL/api/environments"
Best Practices
- Resource Management: Use reasonable TTL values (default: 7 days)
- Image Tagging: Use commit SHAs for reproducible deployments
- Error Handling: Make CI fail if environment deployment fails
- Notifications: Consider adding Slack/Teams notifications on success/failure
- Cleanup: Set up scheduled pipelines to clean up old environments
Security Considerations
- Store API tokens as protected CI/CD variables
- Use minimal scopes for API tokens (
environments:create
only) - Consider IP restrictions for webhook endpoints
- Regularly rotate API tokens
Integration with PR Comments
You can enhance the workflow by adding comments to merge requests:
# Add this to your deploy job
after_script:
- |
if [ "$HTTP_STATUS" -eq 200 ] && [ -n "$APP_URL" ]; then
# Post comment to MR (requires GitLab API token)
curl -X POST \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
-H "Authorization: Bearer $GITLAB_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body": "đ PR Environment deployed: ['$APP_URL'](/developer-docs/docs/setup/05-cicd/'$APP_URL')"}'
fi
This creates a comprehensive CI/CD pipeline that automatically manages ephemeral environments for your pull requests, with proper error handling and user feedback.