GitHub Actions outputs let you pass data between jobs in a workflow.
Let’s see it in action. Imagine a workflow where the first job builds a Docker image and the second job deploys it. The first job needs to tell the second job which image tag was created.
name: Docker Build and Deploy
on: [push]
jobs:
build-image:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.docker_build.outputs.tag }} # Define output for this job
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
id: docker_build # Give this step an ID to reference its outputs
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: true
tags: |
your-dockerhub-username/my-app:latest
your-dockerhub-username/my-app:${{ github.sha }}
# The output of this step can be accessed via steps.docker_build.outputs.<output_name>
# We're not directly defining an output here, but the action itself provides 'tag'
# We'll capture the tag from the action's output and make it a job output.
deploy-image:
runs-on: ubuntu-latest
needs: build-image # This job depends on build-image completing
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy to production
env:
IMAGE_TAG: ${{ needs.build-image.outputs.image-tag }} # Access the output from the previous job
run: |
echo "Deploying image with tag: $IMAGE_TAG"
# Your deployment commands here, using the IMAGE_TAG
# For example, updating a Kubernetes deployment or a serverless function
Here’s the breakdown:
The build-image job has an outputs section. This is where you declare what data this job will expose. We’re defining an output named image-tag. The value for this output comes from a specific step within the build-image job: steps.docker_build.outputs.tag. The docker/build-push-action conveniently provides an output called tag which contains the image tag that was just built and pushed. We’re mapping this step output to a job output.
In the deploy-image job, we first specify needs: build-image. This ensures build-image runs and completes before deploy-image starts. Then, in the steps of deploy-image, we access the output from the build-image job using needs.build-image.outputs.image-tag. This value is then available as an environment variable IMAGE_TAG within the Deploy to production step.
The most surprising thing about outputs is how they are evaluated: they are not computed until the job that produces them has successfully completed. This means you can reference outputs in if conditions for subsequent jobs, effectively creating conditional execution flows based on the results of earlier jobs.
For example, you could have a job that runs tests and outputs a boolean tests_passed. Another job could then use if: needs.test_job.outputs.tests_passed == 'true' to decide whether to proceed with a deployment. This makes your workflows dynamic and responsive.
The outputs themselves are just strings. If you need to pass complex data structures, you’ll typically serialize them into a JSON string before outputting and then deserialize them in the consuming job.
The next concept you’ll run into is how to handle sensitive data or large amounts of data when passing between jobs, which often leads to exploring GitHub Secrets or artifacts.