Skip to content

Deploy with GitHub Actions

This workflow builds one immutable image for each commit. A pull request into main applies it to preview-pr-<number>; a push to main applies it to production. Closing the pull request deletes its complete preview Environment.

The workflow does not create preview infrastructure procedurally. The first apply to a name matching the Project Flow’s preview-* stage forks the configured parent Environment, then reconciles the proposed image on top.

Create a Team-scoped API token under API tokens in the console and add it to the GitHub repository as the DELIBERATE_TOKEN Actions secret. Add the Team slug as the DELIBERATE_TEAM repository variable.

The example assumes the directly-authored Component is named app and its image belongs at registry.deliberate.cloud/<team>/app. Change both names to match the Project.

Create .github/workflows/deliberate.yaml:

name: deliberate
on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened, closed]
permissions:
contents: read
env:
DELIBERATE_TOKEN: ${{ secrets.DELIBERATE_TOKEN }}
DELIBERATE_TEAM: ${{ vars.DELIBERATE_TEAM }}
jobs:
deploy:
if: >-
github.event_name == 'push' ||
(github.event.action != 'closed' &&
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
concurrency:
group: deliberate-${{ github.event.pull_request.number || 'production' }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- name: Install deliberate
run: curl -fsSL https://get.deliberate.cloud | sh
- name: Log Docker into the deliberate registry
run: >-
deliberate registry login
--runtime docker
--team "$DELIBERATE_TEAM"
--name "github-${GITHUB_RUN_ID}"
--days 1
- name: Build and push the immutable image
id: image
run: |
IMAGE="registry.deliberate.cloud/${DELIBERATE_TEAM}/app:git-${GITHUB_SHA}"
docker build --tag "$IMAGE" .
docker push "$IMAGE"
echo "reference=$IMAGE" >> "$GITHUB_OUTPUT"
- name: Select Environment
id: environment
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [ "$GITHUB_EVENT_NAME" = "push" ]; then
echo "name=production" >> "$GITHUB_OUTPUT"
else
echo "name=preview-pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT"
fi
- name: Apply
run: >-
deliberate apply
--env "${{ steps.environment.outputs.name }}"
--image "app=${{ steps.image.outputs.reference }}"
--yes
- name: Wait for readiness
run: >-
deliberate envs status
"${{ steps.environment.outputs.name }}"
--wait
--timeout 10m
- name: Report the application URL
run: |
URL="$(deliberate open --env "${{ steps.environment.outputs.name }}" --print)"
echo "### deliberate. deployment" >> "$GITHUB_STEP_SUMMARY"
echo "$URL" >> "$GITHUB_STEP_SUMMARY"
remove-preview:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
concurrency:
group: deliberate-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- name: Install deliberate
run: curl -fsSL https://get.deliberate.cloud | sh
- name: Remove the complete preview Environment
run: >-
deliberate envs delete
"preview-pr-${{ github.event.pull_request.number }}"
--force

--image is a typed deployment input, not mutable hidden state. Before apply, the CLI merges the image into the in-memory authored tree and shows it in the normal diff. The resolved tree is stored with the Environment revision, so deployment history, rollback, reconciliation, and deliberate pull agree on the image that actually ran. The checked-out files remain unchanged.

Pull requests from forks do not receive repository secrets and are excluded by the deploy condition above. Review and run untrusted contributions without granting them a Team deployment token.