Skip to content

GitHub Actions Artefacts for Asset Deployment

The Problem

Your build process generates assets (compiled CSS, minified JS, built binaries) that are deliberately ignored by Git. Your deployment needs these files, but they don't exist in the repository checkout on the deployment runner.

The Solution

GitHub Actions artefacts provide temporary storage for files between workflow jobs. Build jobs upload generated assets, deployment jobs download them.

Basic Implementation

Upload Assets (Build Job)

Upload artefacts example
- name: Upload build artifacts
  uses: actions/upload-artifact@v4
  with:
    name: build-assets
    path: |
      dist/
      public/build/
      assets/compiled/
    retention-days: 1

Download Assets (Deploy Job)

Download artefacts example
deploy:
  needs: build
  runs-on: ubuntu-latest
  steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Download build artefacts
      uses: actions/download-artifact@v4
      with:
        name: build-assets
        path: .

    - name: Deploy application
      run: |
        # Your deployment script

Multiple Artefacts Strategy

Split different asset types for targeted downloads:

Multiple artefacts workflow
# Build job uploads
- name: Upload frontend CSS
  uses: actions/upload-artifact@v4
  with:
    name: css-assets
    path: httpdocs/app/public/assets/css/

- name: Upload frontend JavaScript
  uses: actions/upload-artifact@v4
  with:
    name: js-assets
    path: httpdocs/app/public/assets/js/

- name: Upload vendor libraries
  uses: actions/upload-artifact@v4
  with:
    name: vendor-assets
    path: httpdocs/app/public/assets/js/vendor/

# Deploy job downloads only what it needs
- name: Get CSS assets
  uses: actions/download-artifact@v4
  with:
    name: css-assets
    path: httpdocs/app/public/assets/css/

- name: Get JavaScript assets
  uses: actions/download-artifact@v4
  with:
    name: js-assets
    path: httpdocs/app/public/assets/js/

Practical Example: Webpack Mix Build System

Complete build and deploy workflow
name: Build and Deploy

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

      - name: Upload compiled assets
        uses: actions/upload-artifact@v4
        with:
          name: build-assets
          path: public/build/
          retention-days: 1

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Download build assets
        uses: actions/download-artifact@v4
        with:
          name: build-assets
          path: public/build/

      - name: Deploy to production
        run: |
          rsync -avz --delete . user@server:/var/www/app/

Key Configuration Options

Retention Period

retention-days: 1  # Minimum for deployments

Default is 90 days. Use 1 day for deployment artefacts to save storage.

Path Handling

path: |
  dist/
  public/assets/
  !public/assets/*.map  # Exclude source maps

Compression

Artefacts are automatically compressed. Large assets benefit significantly.

Common Gotchas

Path Mismatches

The path in upload and download must align with your project structure:

# Upload from webpack mix build output
path: httpdocs/app/public/assets/

# Download to correct location (same path)
path: httpdocs/app/public/assets/

For multi-directory builds, be explicit about what goes where:

# Upload multiple paths
path: |
  httpdocs/app/public/assets/css/
  httpdocs/app/public/assets/js/
  httpdocs/app/build/

# Download preserves structure
path: .  # Downloads to repository root

Job Dependencies

Deployment jobs must explicitly depend on build jobs:

deploy:
  needs: build  # Critical - ensures build completes first

Artefact Names

Names must match exactly between upload and download. Case sensitive.

File Permissions

Downloaded files may lose executable permissions. Restore if needed:

- name: Fix permissions
  run: chmod +x scripts/deploy.sh

Empty Artefacts

If no files match the upload path, the artefact is still created but empty. Check your build actually produces the expected files.

With webpack mix, check your webpack.mix.js outputs to the correct directory:

// webpack.mix.js
const outputFolder = "../httpdocs/app/public/assets";
mix.js("assets/js/app.js", `${outputFolder}/js/`)
   .sass("assets/scss/app.scss", `${outputFolder}/css/`);

Node Version Constraints

Your build may require specific Node.js versions. Pin the version in your workflow:

- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '22.13.0'  # Exact version, not range

This matches your package.json engines requirement and prevents build inconsistencies.

Storage Limits

  • Artefacts count towards your GitHub storage quota
  • Individual artefacts: 10GB maximum
  • Total storage per repository: depends on your plan
  • Use short retention periods for deployment artefacts

Artefact Management

Automatic Cleanup

GitHub automatically deletes artefacts when they expire based on retention-days. You can't recover them after deletion.

Best Practices

Branch-based Retention

  • Development branches: 1-3 days
  • Pull requests: 7 days (until review complete)
  • Main/production: 30-90 days

Size Management

  • Split large artefacts into smaller, targeted ones
  • Exclude unnecessary files (source maps, documentation)
  • Use compression-friendly file structures

Auto-cleanup on Branch Events

Clean up artefacts when branches are merged or deleted:

Branch cleanup workflow
name: Cleanup Branch Artefacts

on:
  pull_request:
    types: [closed]
  delete:
    branches: ['**']

jobs:
  cleanup:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged == true || github.event_name == 'delete'
    steps:
      - name: Delete branch artefacts
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          BRANCH_NAME="${{ github.head_ref || github.ref_name }}"

          # Get artefacts for this branch
          ARTEFACTS=$(gh api repos/${{ github.repository }}/actions/artifacts \
            --jq ".artifacts[] | select(.workflow_run.head_branch==\"$BRANCH_NAME\") | .id")

          # Delete each artefact
          for artifact_id in $ARTEFACTS; do
            echo "Deleting artefact $artifact_id"
            gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id
          done

Production Artefact Strategy

For production deployments, artefacts serve different purposes:

Deployment Assets (Short-term)

retention-days: 1  # Just long enough for deployment

Rollback Assets (Medium-term)

retention-days: 30  # Keep for emergency rollbacks
name: "production-rollback-${{ github.sha }}"

Release Archives (Long-term)

# Use GitHub Releases instead of artefacts for permanent storage
- name: Create Release
  uses: actions/create-release@v1
  with:
    tag_name: v${{ env.VERSION }}
    release_name: Release ${{ env.VERSION }}

Conditional Cleanup Workflow

Smart artefact cleanup workflow
name: Smart Artefact Cleanup

on:
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM
  workflow_dispatch:

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - name: Clean old development artefacts
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Delete artefacts older than 7 days from non-main branches
          CUTOFF_DATE=$(date -d '7 days ago' --iso-8601)

          gh api repos/${{ github.repository }}/actions/artifacts \
            --jq ".artifacts[] | select(.created_at < \"$CUTOFF_DATE\" and .workflow_run.head_branch != \"main\") | .id" \
            | xargs -I {} gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/{}

Manual Management

Delete artefacts early to save storage:

# Using GitHub CLI
gh api repos/{owner}/{repo}/actions/artifacts --jq '.artifacts[] | select(.name=="build-assets") | .id' | xargs -I {} gh api -X DELETE repos/{owner}/{repo}/actions/artifacts/{}

Viewing Artefacts

  • Actions tab: Shows artefacts for each workflow run
  • Storage settings: Repository settings > Actions > General shows total usage
  • API access: Artefacts are accessible via REST API for 90 days (or retention period)

Download Limits

  • Artefacts can be downloaded multiple times during their retention period
  • Downloads don't count against storage quotas
  • Each download creates a zip file containing the artefact contents

Workflow Run Cleanup

When a workflow run is deleted (manually or automatically after 400 days), all its artefacts are deleted regardless of retention period.

Security Considerations

Artifacts are accessible to anyone with repository read access. Don't upload:

  • API keys or secrets
  • Database credentials
  • Private certificates

Use GitHub secrets and environment variables instead.

Debugging Failed Downloads

Common causes:

  1. Artefact name typo
  2. Build job failed (but wasn't obvious)
  3. Incorrect download path
  4. Artefact expired

Check the Actions tab for build job logs and artefact listings.

Possible Next Steps

GitHub Releases for Long-term Storage

  • Free for public and private repositories
  • No storage limits or expiration dates
  • Assets remain private if repository is private
  • Better suited for permanent archives than artefacts
  • Supports any file type: binaries, libraries, documentation, archives
  • Not suitable for container images - use GitHub Container Registry instead

Artefact Optimisation

  • Implement automated cleanup workflows
  • Use matrix builds to reduce artefact duplication
  • Consider external storage (S3, CDN) for large assets

Advanced Patterns

  • Cross-repository artefact sharing via API
  • Artefact promotion between environments
  • Integration with deployment tools (Terraform, Ansible)
Last modified by: Unknown