Workflows often need to share files between jobs or expose build outputs for download. Artifacts serve this role: actions/upload-artifact@v4 stores files under a name: and path:, and a later job retrieves them with actions/download-artifact@v4. Repository artifacts are retained for 90 days by default, after which they are deleted. Each individual artifact is capped at 10 GB and the total upload per workflow run is also limited to 10 GB across all artifacts, so larger outputs need to be handled through GitHub Packages or external storage.
Caching speeds up builds by reusing dependencies between runs. The actions/cache@v4 action stores files keyed by a combination of path and key, with hashFiles() used to generate keys that invalidate automatically when dependency manifests change. For example, a key like \({{ runner.os }}-npm-\){{ hashFiles('**/package-lock.json') }} will produce a fresh cache whenever package-lock.json changes. The key is an exact match for upload and download, while restore-keys: provides comma-separated prefix matches as a fallback when the exact key is missing. Many setup actions, including actions/setup-node@v4 with cache: 'npm' and actions/setup-go@v5 with cache: true, integrate this caching automatically based on lockfile hashes.
Jobs that need databases or other backing services can declare them under services:, which spins up additional containers alongside the job. A PostgreSQL service, for instance, can be started with services: postgres: image: postgres:16 env: POSTGRES_PASSWORD: postgres ports: ['5432:5432'], and health checks added through options: like --health-cmd="pg_isready" ensure the service is ready before the job proceeds. The job and its services share a custom network where services are reachable by their service name as host.