Create Docker Compose Environments from CI Pipelines¶
Tip
If this is your first time using Uffizzi, we recommend reading through Using Uffizzi to see examples of how Uffizzi is configured and managed.
Overview of the process¶
Uffizzi is usually added to the end of your CI pipeline after images have been built and pushed to a container registry. Alternatively, you can use Uffizzi CI if you want to let Uffizzi build and store images for you. In either case, Uffizzi will deploy your application images and manage the environments for you. This guide will walk you through the following steps:
-
Create a Docker Compose template - A template which defines your application configuration and is used as the basis for the Uffizzi Preview Environment.
-
Add a preview step to your CI pipeline - This step defines the lifecycle of your Uffizzi Preview Environments.
-
Configure credentials - Add registry credentials to grant Uffizzi access to pull images.
Create a Docker Compose template¶
In this section, we'll create a template using Docker Compose that describes our application configuration.
Note
Uffizzi supports a subset of the Compose specification. For a full list of supported keywords, see the Uffizzi Compose file reference.
Configure your Compose to dynamically update image definitions¶
The Uffizzi environment creation step typically executes at the end of a CI pipeline after a series of steps that are triggered by an event, such as a pull request or new commit. If you don't have an existing CI solution, Uffizzi CI can build your application from source and store your container images for you (Note: Your source code must be stored in a GitHub repository to use Uffizzi CI). Alternatively, if you're using an external CI service, such as GitHub Actions or GitLab CI, you will need to tell Uffizzi where your images are stored and how to access them.
Each time your CI pipeline builds and pushes new images, Uffizzi needs access to them. This means that we need to dynamically update our compose file service
definitions with the new image names and tags each time our pipeline runs. To do this, we'll follow one of two methods, depending on which CI solution you choose:
-
Uffizzi CI - If you want to use Uffizzi CI, you can simply define a
build
context that points to your source code repository on GitHub and let Uffizzi handle building and tagging images and updating your Compose. See the Uffizzi Compose file reference forbuild
andcontext
details. -
External CI - If you're using an external CI provider such as GitHub Actions or GitLab CI, you can use variable substitution to pass the output from your CI build step, i.e.
image:tag
, to your Compose fileimage
definition (See highlighted example below). This solution is discussed in detail in the next section.
services:
app:
image: "${APP_IMAGE}" # Output of build step stored as environment variable
environment:
PGUSER: "${PGUSER}"
PGPASSWORD: "${PGPASSWORD}"
deploy:
resources:
limits:
memory: 250M
db:
image: postgres:9.6
environment:
POSTGRES_USER: "${PGUSER}"
POSTGRES_PASSWORD: "${PGPASSWORD}"
Define an Ingress for your application¶
Whether using Uffizzi CI or an external CI provider, Uffizzi needs to know which of your application services will receive incoming traffic. This "Ingress" is an HTTPS load balancer that will forward HTTP traffic to one of the defined services
. Along with the service name, you must indicate on which port the target container is listening. The ingress
must be defined within an x-uffizzi
extension field as shown in the example below:
# This block tells Uffizzi which service should receive HTTP traffic.
x-uffizzi:
ingress:
service: app
port: 80
# My application
services:
app:
image: "${APP_IMAGE}" # Output of build step stored as environment variable
environment:
PGUSER: "${PGUSER}"
PGPASSWORD: "${PGPASSWORD}"
deploy:
resources:
limits:
memory: 250M
db:
image: postgres:9.6
environment:
POSTGRES_USER: "${PGUSER}"
POSTGRES_PASSWORD: "${PGPASSWORD}"
Tip
If you need to expose multiple public routes for your application, see this article Exposing multiple routes.
Add secrets in your CI platform (optional)¶
You may also want to move sensitive information like credentials out of your Docker Compose file before commiting it to a remote repository. Most CI providers offer a way to store secrets and then reference them in the steps of your pipeline. To do this, we'll follow one of two methods, depending on which CI solution you choose:
-
Uffizzi CI - If you want to use Uffizzi CI, you can create read-only secrets in the Uffizzi Dashboard web interface (this process is described in detail in the section Configure Credentials), then reference them using the
external
keyword, as shown below. For details onsecrets
andexternal
configuration options, see the Uffizzi Compose file reference. -
External CI - If you're using an external CI provider, you can store the secrets using your provider's interface and then reference them via variable substitution within an
environment
definition (See highlighted example below). This solution is discussed in detail in the next section.
GitHub Actions example
In GitHub, navigate to your repository, then select Settings > Secrets > Actions > New repository secret. Alternatively, you can [use the GitHub CLI](https://cli.github.com/manual/gh_secret).


# This block tells Uffizzi which service should receive HTTPS traffic
x-uffizzi:
ingress:
service: app
port: 80
services:
app:
build:
context: https://github.com/example/app
dockerfile: Dockerfile
secrets:
- pg_user
- pg_password
deploy:
resources:
limits:
memory: 250M
db:
image: postgres:9.6
secrets:
- pg_user
- pg_password
secrets:
pg_user:
external: true
name: "POSTGRES_USER"
pg_password:
external: true
name: "POSTGRES_PASSWORD"
# This block tells Uffizzi which service should receive HTTPS traffic
x-uffizzi:
ingress:
service: app
port: 80
services:
app:
image: "${APP_IMAGE}" # Output of build step stored as environment variable
environment:
PGUSER: "${PGUSER}"
PGPASSWORD: "${PGPASSWORD}"
deploy:
resources:
limits:
memory: 250M
db:
image: postgres:9.6
environment:
POSTGRES_USER: "${PGUSER}"
POSTGRES_PASSWORD: "${PGPASSWORD}"
Commit your template to your repository¶
Once you're finished creating your Docker Compose template, commit it to your repository and push.
Integrate with your CI pipeline¶
In this section, we'll discuss how to integrate the Docker Compose template you created in the previous section with your CI pipeline. If you're using Uffizzi CI, there is no extra configuration required. You can skip to the next section.
If you're using an external CI provider, such as GitHub Actions, GitLab, or CircleCI, you will need to add a step to the end of your pipeline that will use Uffizzi to deploy your application to an on-demand Preview Environment. Exact instructions will vary by provider, so the GitHub Actions guide shown below should be used as a general outline if you're using a different provider.
You can see a complete example workflow using GitHub Actions here.
Output tags from your build step¶
In this step, we'll add a few lines to the build job of our workflow to output the tags of our container images. Later, we'll use these tags in our compose file. In GitHub Actions, this can be done with outputs
, or in GitLab with script
, as highlighted below.
name: Build images and deploy with Uffizzi
on:
push:
branches:
- main
- master
- staging
- qa
pull_request:
types: [opened,reopened,synchronize,closed]
jobs:
# Build and push app image
build-app:
name: Build and Push `app`
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout git repo
uses: actions/checkout@v3
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/${{ github.repository_owner }}/example-app
- name: Build and Push Image to GitHub Container Registry
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
context: ./app
[...]
include:
- "https://gitlab.com/uffizzi/environment-action/raw/main/environment.gitlab-ci.yml"
- "https://gitlab.com/uffizzi/environment-action/raw/main/Notifications/slack.yml"
variables:
REGISTRY: registry.uffizzi.com/$CI_PIPELINE_ID
TAG: latest
VOTE_IMAGE: $REGISTRY/vote:$TAG
WORKER_IMAGE: $REGISTRY/worker:$TAG
RESULT_IMAGE: $REGISTRY/result:$TAG
LOADBALANCER_IMAGE: $REGISTRY/loadbalancer:$TAG
UFFIZZI_COMPOSE_FILE: docker-compose.rendered.yml
.build_image: &build_image
stage: build
image: docker:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- docker build -t $IMAGE $DOCKERFILE_PATH
- docker push $IMAGE
services:
- docker:dind
stages:
- build
- uffizzi_deploy
- uffizzi_notification
- uffizzi_delete
build_vote:
<<: *build_image
variables:
DOCKERFILE_PATH: ./vote
IMAGE: $VOTE_IMAGE
build_worker:
<<: *build_image
variables:
DOCKERFILE_PATH: ./worker
IMAGE: $WORKER_IMAGE
build_result:
<<: *build_image
variables:
DOCKERFILE_PATH: ./result
IMAGE: $RESULT_IMAGE
build_loadbalancer:
<<: *build_image
variables:
DOCKERFILE_PATH: ./loadbalancer
IMAGE: $LOADBALANCER_IMAGE
Render and cache a new compose file¶
Recall that in the previous section, we created a Docker Compose template (docker-compose.uffizzi.yml
) that replaced our static image name with a variable, denoted in the example as image: "${APP_IMAGE}"
. In this step, we'll set and export that variable using the outputs
of the previous job. Additionally, we'll set and export our database secrets that we configured in the preview section.
Next, we'll use the common utility envsubst
and shell I/O redirection (<
, >
) to render a new compose file that includes the image name literal. Finally, we store this rendered compose file in the GitHub Actions cache.
name: Build images and deploy with Uffizzi
on:
push:
branches:
- main
- master
- staging
- qa
pull_request:
types: [opened,reopened,synchronize]
jobs:
# Build and push app image
build-app:
name: Build and Push `app`
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout git repo
uses: actions/checkout@v3
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/${{ github.repository_owner }}/example-app
- name: Build and Push Image to GitHub Container Registry
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
context: ./app
render-compose-file:
name: Render Docker Compose File
runs-on: ubuntu-latest
needs:
- build-app
outputs:
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Render Compose File
run: |
APP_IMAGE=$(echo ${{ needs.build-app.outputs.tags }})
export APP_IMAGE
PGUSER=${{ secrets.PGUSER }}
export PGUSER
PGPASSWORD=${{ secrets.PGPASSWORD }}
export PGPASSWORD
# Render simple template from environment variables.
envsubst < docker-compose.template.yml > docker-compose.rendered.yml
cat docker-compose.rendered.yml
- name: Hash Rendered Compose File
id: hash
run: echo "::set-output name=hash::$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')"
- name: Cache Rendered Compose File
uses: actions/cache@v3
with:
path: docker-compose.rendered.yml
key: ${{ steps.hash.outputs.hash }}
[...]
include:
- "https://gitlab.com/uffizzi/environment-action/raw/main/environment.gitlab-ci.yml"
- "https://gitlab.com/uffizzi/environment-action/raw/main/Notifications/slack.yml"
variables:
REGISTRY: registry.uffizzi.com/$CI_PIPELINE_ID
TAG: latest
VOTE_IMAGE: $REGISTRY/vote:$TAG
WORKER_IMAGE: $REGISTRY/worker:$TAG
RESULT_IMAGE: $REGISTRY/result:$TAG
LOADBALANCER_IMAGE: $REGISTRY/loadbalancer:$TAG
UFFIZZI_COMPOSE_FILE: docker-compose.rendered.yml
.build_image: &build_image
stage: build
image: docker:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- docker build -t $IMAGE $DOCKERFILE_PATH
- docker push $IMAGE
services:
- docker:dind
stages:
- build
- uffizzi_deploy
- uffizzi_notification
- uffizzi_delete
build_vote:
<<: *build_image
variables:
DOCKERFILE_PATH: ./vote
IMAGE: $VOTE_IMAGE
build_worker:
<<: *build_image
variables:
DOCKERFILE_PATH: ./worker
IMAGE: $WORKER_IMAGE
build_result:
<<: *build_image
variables:
DOCKERFILE_PATH: ./result
IMAGE: $RESULT_IMAGE
build_loadbalancer:
<<: *build_image
variables:
DOCKERFILE_PATH: ./loadbalancer
IMAGE: $LOADBALANCER_IMAGE
render_compose_file:
stage: build
image: alpine:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
before_script:
- apk add --no-cache gettext
script:
- envsubst < ./docker-compose.uffizzi.yml > ./$UFFIZZI_COMPOSE_FILE
- echo "$(md5sum $UFFIZZI_COMPOSE_FILE | awk '{ print $1 }')"
- cat $UFFIZZI_COMPOSE_FILE
- echo $CI_PIPELINE_SOURCE
- echo $CI_COMMIT_BRANCH
- echo $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
- echo $CI_ENVIRONMENT_ACTION
- echo $CI_COMMIT_REF_NAME
- echo $CI_COMMIT_REF_SLUG
artifacts:
name: "$CI_JOB_NAME"
paths:
- ./$UFFIZZI_COMPOSE_FILE
Pass rendered compose file from cache to the reusable workflow¶
GitHub Actions¶
Uffizzi publishes a GitHub Actions reusable workflow that can be used to create, update, and delete on-demand test environments given a rendered compose file. This reusable workflow will spin up the Uffizzi CLI on a GitHub Actions runner, which then opens a connection to the Uffizzi platform.
In this final step, we'll pass the cached compose file from the previous step to this reusable workflow. In response, Uffizzi will create a test environment, and post the environment URL as a comment to your pull request issue. This URL will also be available in your environment's containers as the UFFIZZI_URL
environment variable.
This workflow takes as input the following required parameters:
compose-file-cache-key
compose-file-cache-path
server
-https://app.uffizzi.com
or your own Uffizzi API endpoint if you are self-hosting (See next section)
Additionally, this workflow has a few optional parameters if you have configured password protection for your Uffizzi test environments. For instructions on configuring passwords, follow this guide.
url-username
- An HTTP usernameurl-password
- An HTTP password stored as a GitHub Actions secretpersonal-access-token
- Github personal access token with access to theread:packages
scope. This parameter is required only if you use GitHub Container Registry (ghcr.io) to store images.
GitLab CI¶
Uffizzi publishes an environment action that can be used to create, update, and delete ephemeral environments given a rendered compose file. This action will spin up the Uffizzi CLI on a GitLab runner, which then opens a connection to the Uffizzi platform. Be sure to include
this environment action in your gitlab-ci.yml
as shown above:
In this final step, we'll pass the cached compose file from the previous step to this environment action via GitLab environment variables. In response, Uffizzi will create a test environment, and generate a environment URL for your merge requests. This URL will also be available in your environment's containers as the UFFIZZI_URL
environment variable.
name: Build images and deploy with Uffizzi
on:
push:
branches:
- main
- master
- staging
- qa
pull_request:
types: [opened,reopened,synchronize]
jobs:
# Build and push app image
build-app:
name: Build and Push `app`
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout git repo
uses: actions/checkout@v3
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/${{ github.repository_owner }}/example-app
- name: Build and Push Image to GitHub Container Registry
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
context: ./app
render-compose-file:
name: Render Docker Compose File
runs-on: ubuntu-latest
needs:
- build-app
outputs:
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Render Compose File
run: |
APP_IMAGE=$(echo ${{ needs.build-app.outputs.tags }})
export APP_IMAGE
PGUSER=${{ secrets.PGUSER }}
export PGUSER
PGPASSWORD=${{ secrets.PGPASSWORD }}
export PGPASSWORD
# Render simple template from environment variables.
envsubst < docker-compose.template.yml > docker-compose.rendered.yml
cat docker-compose.rendered.yml
- name: Hash Rendered Compose File
id: hash
run: echo "::set-output name=hash::$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')"
- name: Cache Rendered Compose File
uses: actions/cache@v3
with:
path: docker-compose.rendered.yml
key: ${{ steps.hash.outputs.hash }}
# Create and update test environments with Uffizzi
deploy-uffizzi-preview:
name: Use Remote Workflow to Preview on Uffizzi
needs: render-compose-file
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2.1.0
if: ${{ github.event_name == 'pull_request' && github.event.action != 'closed' }}
with:
compose-file-cache-key: ${{ needs.render-compose-file.outputs.compose-file-cache-key }}
compose-file-cache-path: docker-compose.rendered.yml
server: https://app.uffizzi.com
secrets:
personal-access-token: ${{ secrets.GHCR_ACCESS_TOKEN }}
url-username: admin
url-password: ${{ secrets.URL_PASSWORD }}
permissions:
contents: read
pull-requests: write
# Delete test environments with Uffizzi
delete-uffizzi-preview:
name: Use Remote Workflow to Delete an Existing Preview
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2.1.0
if: ${{ github.event_name == 'pull_request' && github.event.action == 'closed' }}
with:
compose-file-cache-key: ''
compose-file-cache-path: docker-compose.rendered.yml
server: https://app.uffizzi.com
permissions:
contents: read
pull-requests: write
# Add the following variables to your project:
# optional:
# - UFFIZZI_SERVER (Uffizzi server URL)
# - UFFIZZI_COMPOSE_FILE (Uffizzi compose file name)
# Include this pipeline in your project
# Add the following stages to your project:
# - uffizzi_deploy
# - uffizzi_delete
variables:
UFFIZZI_IMAGE: uffizzi/cli:v1
UFFIZZI_CURL_IMAGE: apteno/alpine-jq:2022-09-04
UFFIZZI_SERVER: https://app.uffizzi.com
UFFIZZI_COMPOSE_FILE: docker-compose.yml
UFFIZZI_DEPLOY_ENV_FILE: deploy.env
CURL_RETRY: 5
CURL_RETRY_DELAY: 0
CURL_RETRY_MAX_TIME: 60
OIDC_TOKEN: $CI_JOB_JWT
ACCESS_TOKEN: $CI_JOB_TOKEN
deploy_environment:
stage: uffizzi_deploy
image:
name: $UFFIZZI_IMAGE
entrypoint: [ "" ]
rules:
- if: $CI_MERGE_REQUEST_ID
before_script: []
script:
- echo "Checking if environment exists"
- | # Check if environment exists and update if it does
export OIDC_TOKEN=$OIDC_TOKEN
export ACCESS_TOKEN=$ACCESS_TOKEN
if UFFIZZI_ENVIRONMENT_ID=$(/root/docker-entrypoint.sh preview list --filter "gitlab.repo=$CI_PROJECT_PATH gitlab.merge_request.number=$CI_MERGE_REQUEST_IID" | grep deployment-); then
if test "$( wc -l <<< "$UFFIZZI_ENVIRONMENT_ID" )" -gt 1; then
echo "More than one preview found"
exit 1
fi
echo "Updating environment $UFFIZZI_ENVIRONMENT_ID"
ACTION=updated
OUTPUT=$(/root/docker-entrypoint.sh preview update $UFFIZZI_ENVIRONMENT_ID $UFFIZZI_COMPOSE_FILE --output=json --set-labels "gitlab.repo=$CI_PROJECT_PATH gitlab.merge_request.number=$CI_MERGE_REQUEST_IID" | tail -n 1)
else
echo "$UFFIZZI_ENVIRONMENT_ID"
echo "Creating new preview"
ACTION=deployed
OUTPUT=$(/root/docker-entrypoint.sh preview create $UFFIZZI_COMPOSE_FILE --output=json --creation-source=gitlab_actions --set-labels "gitlab.repo=$CI_PROJECT_PATH gitlab.merge_request.number=$CI_MERGE_REQUEST_IID" | tail -n 1)
UFFIZZI_ENVIRONMENT_ID=$(echo $OUTPUT | grep -Eo '"id"[^,]*' | cut -d '"' -f4)
fi
- UFFIZZI_ENVIRONMENT_URL=$(echo $OUTPUT | grep -Eo '"url"[^,]*' | cut -d '"' -f4)
- UFFIZZI_PROXY_URL=$(echo $OUTPUT | grep -Eo '"proxy_url"[^,]*' | cut -d '"' -f4)
- UFFIZZI_CONTAINERS_URI=$(echo $OUTPUT | grep -Eo '"containers_uri"[^,]*' | cut -d '"' -f4)
- echo "ACTION=$ACTION" >> $UFFIZZI_DEPLOY_ENV_FILE
- echo "UFFIZZI_CONTAINERS_URI=$UFFIZZI_CONTAINERS_URI" >> $UFFIZZI_DEPLOY_ENV_FILE
- echo "UFFIZZI_ENVIRONMENT_ID=$UFFIZZI_ENVIRONMENT_ID" >> $UFFIZZI_DEPLOY_ENV_FILE
- echo "UFFIZZI_ENVIRONMENT_URL=$UFFIZZI_ENVIRONMENT_URL" >> $UFFIZZI_DEPLOY_ENV_FILE
- echo "UFFIZZI_PROXY_URL=$UFFIZZI_PROXY_URL" >> $UFFIZZI_DEPLOY_ENV_FILE
- echo "Uffizzi Environment ID:${UFFIZZI_ENVIRONMENT_ID}"
- echo "Uffizzi Environment ${ACTION} at URL:${UFFIZZI_PROXY_URL}"
- echo "Uffizzi Environment deployment details at URI:${UFFIZZI_CONTAINERS_URI}"
environment:
name: "uffizzi/MR-${CI_MERGE_REQUEST_IID}"
url: $UFFIZZI_PROXY_URL
action: start
on_stop: delete_environment
artifacts:
reports:
dotenv: $UFFIZZI_DEPLOY_ENV_FILE
healthcheck_environment:
stage: uffizzi_deploy
image: $UFFIZZI_CURL_IMAGE
needs:
- deploy_environment
rules:
- if: $CI_MERGE_REQUEST_ID
before_script: []
script:
- echo "Healthchecking environment $UFFIZZI_ENVIRONMENT_ID"
- | # Wait for the environment to be healthy. If URL_USERNAME are set, we will use basic auth.
if test -z "$UFFIZZI_URL_USERNAME"; then
curl --retry $CURL_RETRY --retry-delay $CURL_RETRY_DELAY --retry-max-time $CURL_RETRY_MAX_TIME --silent --show-error --fail $UFFIZZI_PROXY_URL > /dev/null
else
curl -u "$UFFIZZI_URL_USERNAME:$UFFIZZI_URL_PASSWORD" --retry $CURL_RETRY --retry-delay $CURL_RETRY_DELAY --retry-max-time $CURL_RETRY_MAX_TIME --silent --show-error --fail $UFFIZZI_PROXY_URL > /dev/null
fi
environment:
name: "uffizzi/MR-${CI_MERGE_REQUEST_IID}"
url: $UFFIZZI_PROXY_URL
delete_environment:
stage: uffizzi_delete
image:
name: $UFFIZZI_IMAGE
entrypoint: [ "" ]
needs:
- deploy_environment
rules:
- if: $CI_MERGE_REQUEST_ID
when: manual
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH
before_script: []
script:
- echo "$UFFIZZI_ENVIRONMENT_ID deleted"
- /root/docker-entrypoint.sh preview delete $UFFIZZI_ENVIRONMENT_ID
environment:
name: "uffizzi/MR-${CI_MERGE_REQUEST_IID}"
action: stop
See the full documentation for this reusable workflow.
Configure credentials¶
In this section, we'll create a Uffizzi Cloud account and connect it with your CI provider.
Uffizzi CI¶
If you're using Uffizzi CI, you will need to link to the Docker Compose template in your GitHub repository from the Uffizzi Dashboard. If you haven't already done so, sign up at Uffizzi Cloud, and then follow the steps to set up your account.
1. Connect the Uffizzi GitHub App
In this step you will install the Uffizzi GitHub App and grant Uffizzi access to the repositories you want to deploy. Login to Uffizzi, then navigate to Account > Settings > General, then select Configure next to the GitHub option.

You'll be asked to Install Uffizzi Cloud, then grant access to your repositories.

Similarly, you can manage the Uffizzi app installation from GitHub by navigating to Settings > Applications > Uffizzi Cloud > Configure

If the Docker Compose template you created in Section 1 references images stored in a private container registry, add those credentials in this step, as indicated in the screenshot below:
2. Add application secrets
If your compose file includes [application secrets](https://docs.uffizzi.com/references/compose-spec/#secrets), such as database credentials, you can add them in the Uffizzi Dashboard. Navigate to your project, then select Specs > Secrets > NEW SECRET. This will open a modal, where you can input your secrets as NAME=VALUE
pairs. Be sure to add one secret per line, separatedy by =
with no white spaces.


Once the secrets are saved, you will not be able to view or edit their values. To make changes to a secret, first delete the old secret, then create a new one.
3. Link to your Docker Compose template
In this final step, we'll link to our Docker Compose template that's stored in our GitHub repository. To do this, navigate to your project, then select Specs > Compose > NEW COMPOSE. Next, select the repository, branch (typically this is the branch you open pull requests against), and name of the compose file. Finally, select VALIDATE & SAVE.

Note, if you did not add your secrets as described in the previous step, you will see a validation error with a link to add your secretes.

Once your compose file has been successfully added, you will see it in the Uffizzi Dashboard with a link to its source on GitHub. Any changes you make to this compose file on GitHub will be synced in the Uffizzi Dashboard.

That's it! Uffizzi is now configured with your Docker Compose template. To test your setup, you can manually deploy your primary branch to an on-demand test environment using the Test Compose button in the Uffizzi Dashboard, or try opening a pull request on GitHub to deploy a feature branch.
Connect container registy credentials to Uffizzi¶
Follow this section if you're using an external container registry, such as GHCR, ECR, or Docker Hub, to store your built images (i.e. You are not relying on Uffizz CI to storage images for you).
How you add container registry credentials to Uffizzi depends on your registry of choice.
GitHub Container Registry (ghcr.io)¶
If you use GitHub Container Registry (ghcr.io), you will need to generate a GitHub personal access token with access to the read:packages
scope. Once this token is generated, add it as a GitHub repository secret, then pass this value to the reusable workflow using the personal-access-token
parameter, as described in the previous section.
Once you've created a personal access token, you should add it in your Uffizzi Account Settings.
Add GHCR personal access token in Account Settings
Login to Uffizzi, then navigate to Account > Settings > Registries, then select Configure next to the GHCR option.

Enter your GitHub username and personal access token, then select Sign in to GitHub Container Registry.
ECR, ACR, GCR, Docker Hub¶
If you use Amazon ECR, Azure Container Registry (ACR), Google Container Registry (GCR), or Docker Hub, you should add your credentials as GitHub repository secrets or GitLab masked CI/CD variables.
See this AWS ECR example
If you use Amazon ECR, Azure Container Registry (ACR), Google Container Registry (GCR), or Docker Hub, you should add your credentials as GitHub repository secrets. In the highlighted example below, AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
are used:
[...]
jobs:
# Build and push app image
build-app:
name: Build and Push `app`
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Login to ECR
uses: docker/login-action@v2
with:
registry: 263049488290.dkr.ecr.us-east-1.amazonaws.com
username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Checkout git repo
uses: actions/checkout@v3
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: 263049488290.dkr.ecr.us-east-1.amazonaws.com/app
- name: Build and Push Image to ECR
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
context: ./app
[...]
Now, we need to add these same credentials in the Uffizzi Dashboard. In Step 3 of 4 of the account setup guide, you are asked to connect to various external services, as shown below. Select the Sign in option for your registry provider(s) of choice, then enter your credentials. For example, to add AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
, select Sign in to Amazon Elastic Container Registry.

After account setup, you can make changes to your credentials by selecting Menu (three horizontal lines) > Settings > Integrations > CONFIGURE/DISCONNECT.

That's it! Your pipeline is now configured to use Uffizzi. To test your pipeline, try opening a new pull request.