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
Download Assets (Deploy Job)
Download artefacts example
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
Default is 90 days. Use 1 day for deployment artefacts to save storage.
Path Handling
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:
Artefact Names
Names must match exactly between upload and download. Case sensitive.
File Permissions
Downloaded files may lose executable permissions. Restore if needed:
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)
Rollback Assets (Medium-term)
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:
- Artefact name typo
- Build job failed (but wasn't obvious)
- Incorrect download path
- 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)