Mastering GitHub Actions: Uploading and Downloading Build Artifacts

In a typical CI/CD pipeline, jobs often run in isolation on different virtual machines. This means that any file created in one job—such as a compiled Java JAR file, a test report, or a distribution package—is lost once that job finishes. To solve this, GitHub Actions provides a mechanism called Artifacts.

Artifacts allow you to persist data after a job has completed and share that data with another job in the same workflow run. This lesson covers how to effectively use the upload-artifact and download-artifact actions to manage your build outputs.

Understanding the Artifact Workflow

Think of artifacts as a temporary storage locker. One job puts something in the locker (Upload), and another job takes it out to use it (Download). Once the workflow run is entirely finished, you can also download these files manually from the GitHub UI for inspection.

[ Job 1: Build ] 
      |
      |--> (Step: Compile Code)
      |--> (Step: Run Tests)
      |--> (Step: Upload Artifact) --> [ GitHub Storage ]
                                              |
[ Job 2: Deploy ]                             |
      |                                       |
      |--> (Step: Download Artifact) <--------|
      |--> (Step: Deploy to Server)
    

Uploading Build Artifacts

To save files from your runner to GitHub's servers, we use the actions/upload-artifact action. This is commonly used for compiled binaries, log files, or HTML test coverage reports.

Basic Syntax

The upload action requires a name for the artifact and the path to the file or directory you want to save.

- name: Upload Build Artifact
  uses: actions/upload-artifact@v4
  with:
    name: production-binary
    path: target/*.jar
    retention-days: 5
    
  • name: The identifier for the artifact. You will use this name to download it later.
  • path: The location of the file(s). You can use wildcards like *.jar or specify a directory.
  • retention-days: (Optional) How long GitHub should keep the file before deleting it to save space.

Downloading Build Artifacts

To retrieve a file uploaded by a previous job, we use the actions/download-artifact action. This is essential when you have a multi-job workflow where the "Deploy" job depends on the "Build" job.

- name: Download Build Artifact
  uses: actions/download-artifact@v4
  with:
    name: production-binary
    path: output-directory/
    

If you do not specify a path in the download action, the files will be downloaded into the current working directory.

Practical Java Example: Build and Package

In this example, we have two jobs. The first job compiles a Java project using Maven and uploads the JAR file. The second job downloads that JAR to simulate a deployment step.

name: Java CI with Artifacts

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      
      - name: Build with Maven
        run: mvn clean package

      - name: Archive Production Artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

  test-reports:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Get Build Artifact
        uses: actions/download-artifact@v4
        with:
          name: app-jar
      
      - name: List Files
        run: ls -R
    

Common Mistakes to Avoid

  • Case Sensitivity: Artifact names are case-sensitive. If you upload as "App-Jar" and try to download "app-jar", the action will fail.
  • Path Errors: Ensure the path provided in the upload step actually exists. If your build fails and doesn't produce a file, the upload step might fail or upload an empty folder.
  • Job Dependencies: You cannot download an artifact in a job that runs in parallel with the upload job. Use the needs: job_name keyword to ensure the upload finishes first.
  • Disk Space: Large artifacts can quickly consume your GitHub storage quota. Always set a retention-days value for temporary files.

Real-World Use Cases

  • Deployment Binaries: Compiling code once and deploying the exact same binary to Staging and Production environments.
  • Test Evidence: Saving screenshots from failed Selenium or Cypress UI tests for debugging.
  • Security Scans: Storing PDF or JSON reports from vulnerability scanners for compliance auditing.
  • Log Persistence: Uploading verbose system logs when a build fails to help developers troubleshoot offline.

Interview Notes: Artifacts vs. Caching

A common interview question is: "What is the difference between Artifacts and Caching in GitHub Actions?"

  • Artifacts: Used to share data between jobs in the same workflow run or to store build outputs for manual download. They are unique to every run.
  • Caching: Used to reuse files (like dependencies in node_modules or .m2) across different workflow runs to speed up the build process.

Summary

Mastering artifacts is a crucial step in building professional CI/CD pipelines. By using actions/upload-artifact and actions/download-artifact, you bridge the gap between isolated jobs, ensuring that your build outputs are safely stored and easily accessible for deployment or debugging.

In the next lesson, we will explore Caching Dependencies to optimize our workflow execution time. Check out the GitHub Actions Introduction if you need a refresher on basic syntax.