.gitlab-ci.yml
Introduction¶
CI/CD pipelines are the assembly lines of modern software development. But unlike factory floors, these assembly lines often carry invisible baggage: leftover artifacts, polluted caches, and mysterious state that makes builds flaky and slow.
The work/ directory pattern solves this by establishing a structured, performance-optimized workspace that treats your CI/CD pipeline like a high-speed conveyor belt:
- Feed it raw materials from your source repository
- Cache reusable tools to avoid redundant downloads
- Produce build artifacts in isolated directories
- Ship test reports and deployable packages
- Wipe the belt clean for the next run
This isn't just about organizationโit's about reproducibility, speed, and safety. When implemented correctly, the work/ pattern can reduce build times by 50-80% while eliminating entire classes of "it works on my machine" failures.
๐ Why This Matters¶
Modern CI/CD faces three competing pressures:
- Speed: Developers expect sub-5-minute feedback cycles
- Reliability: Flaky tests and non-deterministic builds destroy confidence
- Cost: Cloud CI minutes translate directly to infrastructure expenses
The work/ directory pattern addresses all three:
- Speed: Aggressive caching of dependencies, smart parallelization, and RAM disk mounting
- Reliability: Ephemeral workspaces eliminate state carryover between runs
- Cost: Faster builds = fewer CI minutes = lower bills
Traditional approaches mix build outputs, caches, and temporary files into unpredictable locations. The work/ pattern provides a contractโa structured agreement between your build scripts and your CI infrastructure about where things live and what gets preserved.
๐ฏ What You'll Learn¶
By the end of this article, you'll be able to:
- Architect
work/directory structures that optimize CI/CD performance - Implement selective caching strategies that maximize speed without compromising reproducibility
- Configure
work/integration across GitHub Actions, GitLab CI, CircleCI, and Jenkins - Establish automated guardrails to prevent common pitfalls
- Measure and continuously optimize pipeline performance
๐ Before We Begin¶
This article assumes you:
- Have experience with at least one CI/CD platform
- Understand basic caching concepts (cache keys, invalidation, TTL)
- Can read YAML configuration and shell scripts
- Are familiar with build tool concepts (dependency resolution, compilation, artifact generation)
We'll use GitHub Actions for primary examples, but the patterns apply universally across platforms.
1. The work/ Directory Philosophy¶
1.1 Core Principles¶
The work/ directory is built on five foundational principles:
| Principle | Meaning | Impact |
|---|---|---|
| Ephemeral per Job | No state carries over between pipeline runs | Reproducible builds, no flakiness from leftover files |
| Cache Selectively | Only preserve work/cache/, never work/build/ |
Fast dependency restoration without polluting outputs |
| Mount as Volume | Keep work/ outside container layers when possible |
Better I/O performance, easier cleanup |
| Clean on Exit | Remove disposable directories after each job | Prevent disk exhaustion on shared runners |
| Never Commit | .gitignore everything under work/ |
Protect repos from accidentally committed binaries |
1.2 The Directory Contract¶
Think of work/ as a standardized API between your build logic and your infrastructure:
work/ # โ Root workspace (mounted, never committed)
โโโ cache/ # โ PERSISTENT: Survives job runs
โ โโโ npm/ # npm/yarn/pnpm caches
โ โโโ pip/ # Python package cache
โ โโโ maven/ # Java .m2 repository
โ โโโ gradle/ # Gradle build cache
โ โโโ docker/ # Docker BuildKit cache
โ
โโโ build/ # โ EPHEMERAL: Regenerated every run
โ โโโ classes/ # Compiled binaries
โ โโโ dist/ # Distribution packages
โ โโโ reports/ # Test/coverage reports โ ARTIFACT
โ
โโโ runtime/ # โ JOB-LOCAL: Services for this run only
โ โโโ test-db/ # Temporary PostgreSQL instance
โ โโโ mock-api/ # Local API server for tests
โ
โโโ temp/ # โ DISPOSABLE: Cleaned every run
โโโ ci-logs/ # Verbose build logs
โโโ scratch/ # Temporary computation files
Key Insight: The structure tells you the lifecycle of each subdirectory:
cache/= preserve across runs (cache key)build/= regenerate every run (artifact source)runtime/= scoped to job execution (cleanup on exit)temp/= fully disposable (aggressive cleanup)
2. CI/CD Integration Strategy¶
2.1 GitHub Actions Implementation¶
Here's a production-ready GitHub Actions workflow using the work/ pattern:
name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
env:
WORK_DIR: {% raw %}${{ github.workspace }}/work{% endraw %}
CACHE_DIR: {% raw %}${{ github.workspace }}/work/cache{% endraw %}
BUILD_DIR: {% raw %}${{ github.workspace }}/work/build{% endraw %}
steps:
# 1. Checkout repository
- uses: actions/checkout@v4
# 2. Initialize work/ structure
- name: Setup work directory
run: |
mkdir -p work/{cache/{npm,pip,maven},build/{dist,reports},runtime,temp}
echo "Work directory structure:"
tree -L 2 work/ || ls -R work/
# 3. Restore dependency cache (only work/cache/)
- name: Restore dependency cache
uses: actions/cache@v4
with:
path: work/cache/**
key: {% raw %}${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/requirements.txt', '**/pom.xml') }}{% endraw %}
restore-keys: |
{% raw %}${{ runner.os }}-deps-{% endraw %}
# 4. Install dependencies (cached)
- name: Install dependencies
run: |
npm install --cache $CACHE_DIR/npm --prefer-offline
pip install -r requirements.txt --cache-dir $CACHE_DIR/pip
# 5. Build โ work/build/
- name: Build application
run: |
npm run build -- --output-path=$BUILD_DIR/dist
echo "Build artifacts:"
du -sh $BUILD_DIR/dist
# 6. Run tests โ work/build/reports/
- name: Run tests
run: |
pytest \
--cache-dir=$CACHE_DIR/pytest \
--junitxml=$BUILD_DIR/reports/junit.xml \
--cov --cov-report=xml:$BUILD_DIR/reports/coverage.xml
# 7. Upload test reports as artifacts
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: work/build/reports/
retention-days: 7
# 8. Upload build artifacts (for deployment)
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: work/build/dist/
retention-days: 30
# 9. Cleanup disposable directories
- name: Cleanup
if: always()
run: |
rm -rf work/temp work/runtime work/build
echo "Remaining work/ size:"
du -sh work/ || echo "work/ cleaned"
Why This Works:
- Lines 7-9: Environment variables make paths configurable and self-documenting
- Lines 17-18: Explicit directory creation prevents "directory not found" errors
- Lines 21-26: Only
work/cache/is cached; build outputs regenerate fresh each time - Lines 51-57: Test reports uploaded as artifacts for debugging, not cached
- Lines 67-71: Aggressive cleanup prevents disk exhaustion on shared runners
2.2 GitLab CI Implementation¶
GitLab CI uses a different syntax but the same principles:
# .gitlab-ci.yml
variables:
WORK_DIR: "${CI_PROJECT_DIR}/work"
CACHE_DIR: "${CI_PROJECT_DIR}/work/cache"
BUILD_DIR: "${CI_PROJECT_DIR}/work/build"
cache:
key:
files:
- package-lock.json
- requirements.txt
paths:
- work/cache/
stages:
- setup
- build
- test
before_script:
- mkdir -p work/{cache/{npm,pip},build/{dist,reports},runtime,temp}
setup:
stage: setup
script:
- npm install --cache $CACHE_DIR/npm --prefer-offline
- pip install -r requirements.txt --cache-dir $CACHE_DIR/pip
build:
stage: build
script:
- npm run build -- --output-path=$BUILD_DIR/dist
artifacts:
paths:
- work/build/dist/
expire_in: 30 days
test:
stage: test
script:
- pytest --cache-dir=$CACHE_DIR/pytest --junitxml=$BUILD_DIR/reports/junit.xml
artifacts:
reports:
junit: work/build/reports/junit.xml
paths:
- work/build/reports/
expire_in: 7 days
after_script:
- rm -rf work/{temp,runtime,build}
2.3 CircleCI Implementation¶
CircleCI's orb-based approach:
version: 2.1
orbs:
node: circleci/node@5.0
jobs:
build-and-test:
docker:
- image: cimg/node:18.0
environment:
WORK_DIR: ~/work
CACHE_DIR: ~/work/cache
BUILD_DIR: ~/work/build
steps:
- checkout
- run:
name: Setup work directory
command: mkdir -p work/{cache/npm,build/{dist,reports},temp}
- restore_cache:
keys:
- {% raw %}v1-deps-{{ checksum "package-lock.json" }}{% endraw %}
- v1-deps-
- run:
name: Install dependencies
command: npm install --cache $CACHE_DIR/npm
- save_cache:
key: {% raw %}v1-deps-{{ checksum "package-lock.json" }}{% endraw %}
paths:
- work/cache/npm
- run:
name: Build
command: npm run build -- --output-path=$BUILD_DIR/dist
- run:
name: Test
command: npm test -- --outputFile=$BUILD_DIR/reports/junit.xml
- store_test_results:
path: work/build/reports/
- store_artifacts:
path: work/build/dist/
workflows:
build-test-deploy:
jobs:
- build-and-test
3. Caching Strategy: What to Cache, What to Regenerate¶
The golden rule: Cache inputs, not outputs.
3.1 Caching Decision Matrix¶
| Directory | Cache? | Cache Key Strategy | TTL | Reasoning |
|---|---|---|---|---|
work/cache/npm/ |
โ Yes | hash(package-lock.json) |
7 days | Dependencies rarely change; downloading is slow |
work/cache/pip/ |
โ Yes | hash(requirements.txt) |
7 days | PyPI downloads can be expensive |
work/cache/maven/ |
โ Yes | hash(pom.xml) |
14 days | Maven central is sometimes slow |
work/cache/gradle/ |
โ Yes | hash(**/*.gradle, gradle.properties) |
7 days | Gradle daemon and dependency cache |
work/cache/docker/ |
โ Yes | hash(Dockerfile, docker-compose.yml) |
3 days | BuildKit layer cache |
work/build/dist/ |
โ No | N/A | N/A | Outputs must be reproducible from source |
work/build/reports/ |
โ No | N/A | N/A | Test results should reflect current code |
work/runtime/ |
โ No | N/A | N/A | Job-local services, not persistent |
work/temp/ |
โ No | N/A | N/A | Fully disposable scratch space |
3.2 Advanced Caching Patterns¶
Multi-Layer Cache Keys¶
Use fallback keys for partial cache hits:
- uses: actions/cache@v4
with:
path: work/cache/**
key: {% raw %}${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}{% endraw %}
restore-keys: |
{% raw %}${{ runner.os }}-${{ matrix.node-version }}-{% endraw %}
{% raw %}${{ runner.os }}-{% endraw %}
Why: If package-lock.json changes, you still get the OS-level cache (better than nothing).
Partitioned Caches by Language/Tool¶
- name: Cache Node.js dependencies
uses: actions/cache@v4
with:
path: work/cache/npm
key: {% raw %}npm-${{ hashFiles('package-lock.json') }}{% endraw %}
- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: work/cache/pip
key: {% raw %}pip-${{ hashFiles('requirements.txt') }}{% endraw %}
Why: Independent invalidationโchanging Python deps doesn't bust your npm cache.
BuildKit Docker Layer Caching¶
# In Dockerfile
# syntax=docker/dockerfile:1.4
FROM node:18 AS builder
# Mount cache during build
RUN --mount=type=cache,target=/root/.npm \
npm install --prefer-offline
# Output goes to work/build/dist (not cached)
COPY . /app
RUN npm run build -- --output-path=/app/work/build/dist
Then in CI:
- name: Build Docker image with cache
run: |
docker buildx build \
--cache-from type=local,src=work/cache/docker \
--cache-to type=local,dest=work/cache/docker \
-t myapp:latest .
4. Performance Optimization Techniques¶
4.1 RAM Disk Mounting (3-5ร Faster Builds)¶
On Linux/macOS, mount work/ as a tmpfs (RAM disk):
- name: Mount work/ as tmpfs
run: |
sudo mkdir -p /mnt/ramdisk
sudo mount -t tmpfs -o size=4G tmpfs /mnt/ramdisk
ln -s /mnt/ramdisk work
Impact: Compilation-heavy builds (C++, Rust, large TypeScript) see 3-5ร speedups because disk I/O is eliminated.
Caveat: You lose everything if the job crashesโensure critical artifacts are copied out before job end.
4.2 Parallel Builds in Partitions¶
Split large projects into parallel build partitions:
- name: Parallel build
run: |
mkdir -p work/build/partitions/{frontend,backend,api}
npm run build:frontend -- --output-path=work/build/partitions/frontend &
npm run build:backend -- --output-path=work/build/partitions/backend &
npm run build:api -- --output-path=work/build/partitions/api &
wait
# Merge artifacts
cp -r work/build/partitions/* work/build/dist/
4.3 Pre-Warming Caches in Setup Jobs¶
For monorepos or multi-stage pipelines:
jobs:
setup-cache:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Pre-install dependencies
run: npm install --cache work/cache/npm
- uses: actions/cache/save@v4
with:
path: work/cache/npm
key: {% raw %}npm-${{ hashFiles('package-lock.json') }}{% endraw %}
build:
needs: setup-cache
runs-on: ubuntu-latest
steps:
- uses: actions/cache/restore@v4
# ... rest of build
Why: The build job starts with a warm cache, skipping slow network calls.
5. Guardrails and Validation¶
5.1 Enforce .gitignore Rules¶
Prevent accidental commits of work/ contents:
# .github/workflows/validate-work.yml
name: Validate work/ Directory
on: [push, pull_request]
jobs:
check-gitignore:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Verify work/ is gitignored
run: |
if ! grep -q "^work/$" .gitignore; then
echo "ERROR: work/ must be in .gitignore"
exit 1
fi
- name: Fail if work/ in commit
run: |
if git diff --cached --name-only | grep -q "^work/"; then
echo "ERROR: work/ files should not be committed"
exit 1
fi
5.2 Disk Space Monitoring¶
Prevent disk exhaustion:
- name: Monitor disk usage
if: always()
run: |
df -h
du -sh work/ || echo "work/ already cleaned"
# Auto-cleanup old caches if disk > 90% full
if [ $(df / | tail -1 | awk '{print $5}' | sed 's/%//') -gt 90 ]; then
echo "Disk usage > 90%, cleaning old caches..."
find work/cache -type f -mtime +7 -delete
fi
5.3 Cache Invalidation Tests¶
Ensure your cache strategy works:
- name: Test cache invalidation
run: |
# Bust the cache by modifying package-lock.json
echo "# cache-buster" >> package-lock.json
# Verify cache miss on next run
npm install --cache work/cache/npm --verbose | tee install.log
if grep -q "cache hit" install.log; then
echo "ERROR: Cache should have been invalidated"
exit 1
fi
6. Automation Rules for AI Agents¶
If you're building AI-powered CI/CD orchestration, encode these rules:
| Trigger Event | AI Agent Action |
|---|---|
| Pipeline start | mkdir -p work/{cache,build,runtime,temp} |
| Dependency install | Use --cache-dir=work/cache/<tool> |
| Build step | Output to work/build/dist |
| Test execution | Write reports to work/build/reports |
| Artifact upload | Source from work/build/reports/** or work/build/dist |
| Job end | rm -rf work/{temp,runtime,build} |
| Disk usage > 90% | find work/cache -type f -mtime +7 -delete |
| Cache miss | Log warning, proceed with fresh install |
| Build failure | Preserve work/build/reports for debugging |
Example: AI-Powered Cache Strategy Optimization¶
# Pseudo-code for AI agent
def optimize_cache_strategy(pipeline_history):
for job in pipeline_history:
if job.cache_restore_time > 60: # seconds
suggest_partition_cache_by_language(job)
if job.cache_hit_rate < 0.5:
suggest_refine_cache_key(job)
if job.build_time > job.cache_restore_time * 5:
suggest_ram_disk_mount(job)
7. Measuring Success: Metrics to Track¶
7.1 Key Performance Indicators¶
| Metric | Measurement | Target |
|---|---|---|
| Total build time | Job duration (start to finish) | < 5 minutes |
| Cache hit rate | (cache hits / total builds) ร 100 | > 80% |
| Cache restore time | Time to restore work/cache/ |
< 30 seconds |
| Artifact size | Size of uploaded work/build/ artifacts |
< 500 MB |
| Disk usage peak | Max work/ size during job |
< 4 GB |
| Build reproducibility | % of builds passing after cache bust | > 95% |
7.2 Benchmark Script¶
#!/usr/bin/env bash
# benchmark-work-pattern.sh
set -euo pipefail
echo "=== Benchmarking work/ Pattern ==="
# 1. Measure cold build (no cache)
rm -rf work/cache
time npm install --cache work/cache/npm > /dev/null 2>&1
COLD_BUILD=$?
# 2. Measure warm build (with cache)
time npm install --cache work/cache/npm --prefer-offline > /dev/null 2>&1
WARM_BUILD=$?
# 3. Measure cache size
CACHE_SIZE=$(du -sh work/cache | cut -f1)
# 4. Measure build output size
npm run build -- --output-path=work/build/dist
BUILD_SIZE=$(du -sh work/build/dist | cut -f1)
echo "Results:"
echo " Cold build: ${COLD_BUILD}s"
echo " Warm build: ${WARM_BUILD}s"
echo " Cache size: ${CACHE_SIZE}"
echo " Build output: ${BUILD_SIZE}"
echo " Speedup: $(echo "scale=2; $COLD_BUILD / $WARM_BUILD" | bc)x"
Run this before and after implementing the work/ pattern to quantify improvements.
8. Multi-Platform Examples¶
8.1 Jenkins Pipeline¶
pipeline {
agent any
environment {
WORK_DIR = "${WORKSPACE}/work"
CACHE_DIR = "${WORKSPACE}/work/cache"
BUILD_DIR = "${WORKSPACE}/work/build"
}
stages {
stage('Setup') {
steps {
sh 'mkdir -p work/{cache/npm,build/dist,temp}'
}
}
stage('Restore Cache') {
steps {
// Jenkins uses stash/unstash
unstash 'npm-cache'
}
}
stage('Build') {
steps {
sh 'npm install --cache $CACHE_DIR/npm'
sh 'npm run build -- --output-path=$BUILD_DIR/dist'
}
}
stage('Test') {
steps {
sh 'npm test -- --outputFile=$BUILD_DIR/reports/junit.xml'
}
}
stage('Archive') {
steps {
archiveArtifacts artifacts: 'work/build/dist/**', fingerprint: true
junit 'work/build/reports/junit.xml'
}
}
stage('Save Cache') {
steps {
stash name: 'npm-cache', includes: 'work/cache/npm/**'
}
}
}
post {
always {
sh 'rm -rf work/{temp,runtime,build}'
}
}
}
8.2 Azure Pipelines¶
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
WORK_DIR: '$(Pipeline.Workspace)/work'
CACHE_DIR: '$(Pipeline.Workspace)/work/cache'
BUILD_DIR: '$(Pipeline.Workspace)/work/build'
steps:
- checkout: self
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
path: $(CACHE_DIR)/npm
restoreKeys: |
npm | "$(Agent.OS)"
- script: |
mkdir -p work/{cache/npm,build/dist,temp}
displayName: 'Setup work directory'
- script: |
npm install --cache $(CACHE_DIR)/npm
displayName: 'Install dependencies'
- script: |
npm run build -- --output-path=$(BUILD_DIR)/dist
displayName: 'Build application'
- script: |
npm test -- --outputFile=$(BUILD_DIR)/reports/junit.xml
displayName: 'Run tests'
- task: PublishTestResults@2
inputs:
testResultsFiles: '$(BUILD_DIR)/reports/junit.xml'
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(BUILD_DIR)/dist'
artifactName: 'dist'
- script: |
rm -rf work/{temp,runtime,build}
displayName: 'Cleanup'
condition: always()
9. Advanced Patterns¶
9.1 Containerized Builds with work/ Mounting¶
# docker-compose.ci.yml
version: '3.8'
services:
builder:
image: node:18
volumes:
- ./:/app
- ./work/cache/npm:/root/.npm:cached
- ./work/build:/app/build
working_dir: /app
command: npm run build -- --output-path=/app/build/dist
Then in CI:
- name: Build in container
run: docker-compose -f docker-compose.ci.yml up --abort-on-container-exit
Why: Isolates build environment while preserving cache between runs.
9.2 Multi-Stage Builds with Shared Cache¶
# syntax=docker/dockerfile:1.4
FROM node:18 AS builder
WORKDIR /app
# Mount cache during dependency install
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
# Copy source and build
COPY . .
RUN npm run build -- --output-path=/app/work/build/dist
# Production stage (no cache, minimal size)
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/work/build/dist ./dist
CMD ["node", "dist/server.js"]
โ Validation and Practice¶
Exercise 1: Refactor a Pipeline to Use work/¶
- Choose an existing CI/CD pipeline
- Identify where dependencies are installed and where build outputs go
- Refactor to use the
work/structure: - Dependencies โ
work/cache/ - Build outputs โ
work/build/ - Test reports โ
work/build/reports/ - Measure build time before and after
- Test cache invalidation by modifying a lock file
Success Criteria: - [ ] Build time reduced by >30% on cached runs - [ ] No build outputs committed to git - [ ] Cache hit rate >80% over 10 runs
Exercise 2: Implement Guardrails¶
- Add a
.gitignorevalidation workflow - Implement disk usage monitoring
- Add cache invalidation tests
- Set up build time alerting (if build > 10 minutes)
Success Criteria:
- [ ] Attempting to commit work/ fails in pre-commit hook
- [ ] Disk usage warnings appear when >85% full
- [ ] Cache busting is detected and logged
Exercise 3: Optimize for Speed¶
- Benchmark your current pipeline
- Implement one optimization:
- RAM disk mounting
- Partitioned caches
- Parallel build stages
- Re-benchmark and compare
Success Criteria: - [ ] Documented before/after metrics - [ ] >50% speedup on at least one stage - [ ] Reproducibility maintained (same outputs)
๐ง Troubleshooting Guide¶
Common Issues and Solutions¶
Issue 1: Cache Restored But Build Still Slow¶
Symptoms: Cache shows as "restored" but dependencies still download Causes: Cache key collision, cache corruption, or wrong cache path Solution:
# Add debug logging
- name: Debug cache
run: |
echo "Cache should be at: $CACHE_DIR/npm"
ls -lah $CACHE_DIR/npm || echo "Cache empty!"
npm config get cache
Issue 2: Disk Space Exhausted¶
Symptoms: "No space left on device" errors Causes: Large caches, accumulating work/ directories, Docker layer bloat Solution:
# Emergency cleanup
find work/ -type f -mtime +1 -delete # Delete files >1 day old
docker system prune -af # Clean Docker
df -h # Verify space freed
Issue 3: Flaky Builds After work/ Migration¶
Symptoms: Tests pass locally, fail in CI, or vice versa Causes: Incorrect paths, missing environment variables, cache pollution Diagnosis:
# Compare environments
echo "=== Local ==="
pwd
echo $WORK_DIR
ls -R work/
echo "=== CI ==="
# (copy output from CI logs)
๐ Next Steps and Further Learning¶
๐ฎ Advanced Topics to Explore¶
- Distributed Caching: Share caches across teams using centralized storage (S3, Azure Blob)
- Incremental Builds: Only rebuild changed modules (Nx, Turborepo, Bazel)
- Remote Execution: Run builds on dedicated clusters (BuildBuddy, Earthly)
- Reproducible Builds: Achieve bit-for-bit identical outputs across environments
๐ Recommended Learning Path¶
- Foundation Building: Master your CI/CD platform's caching mechanisms
- Skill Expansion: Learn Docker BuildKit, multi-stage builds, and layer optimization
- Specialization Options: Explore Bazel, Nix, or Pants for hermetic builds
๐ Community and Resources¶
- GitHub Actions Cache Documentation
- GitLab CI/CD Cache Best Practices
- Docker BuildKit Cache Mounts
- Reproducible Builds Project
๐ฏ Project Ideas¶
- Beginner Project: Refactor a simple Node.js project to use
work/ - Intermediate Project: Implement cross-platform caching for a monorepo
- Advanced Project: Build a self-optimizing CI pipeline that adjusts cache strategies based on historical data
๐ Resources and References¶
๐ Essential Documentation¶
- GitHub Actions: Caching Dependencies
- GitLab CI: Cache
- CircleCI: Caching Strategies
- Docker BuildKit Cache Mounts
๐ฅ Video and Interactive Resources¶
๐ฌ Community Support¶
- Stack Overflow: CI/CD
- r/devops - CI/CD discussions and troubleshooting
- DevOps Discord Communities
๐ง Tools and Utilities¶
- actions/cache - GitHub Actions caching action
- cache-buildkit - Docker BuildKit with advanced caching
- turbo - High-performance build system with intelligent caching
๐ Templates and Examples¶
Final Thoughts¶
The work/ directory pattern transforms CI/CD from a source of frustration into a competitive advantage. By treating your pipeline as a structured, stateful-but-disposable conveyor belt, you gain:
- Predictability: Same inputs โ same outputs, every time
- Speed: Aggressive caching without fragility
- Safety: No state leakage, no mysterious failures
- Observability: Clear structure makes debugging straightforward
Start small:
- Create the directory structure
- Move one build output to
work/build/ - Cache one dependency directory in
work/cache/ - Measure the improvement
- Iterate and expand
Within a few sprints, you'll wonder how you ever shipped software without it.
Last Updated: November 16, 2025 โ IT-Journey Team