Jenkins Build Archive app icon
Open source · Independent of Jenkins · Self-hosted · Docker deploy

Jenkins Build Archive

Jenkins cleaned up the builds. The traceability is still here.

A build metadata archive that runs independently of Jenkins: when a pipeline ends it stores the Git branch, commit, Docker image tag and digest, so after old Build Records are cleaned up you can still answer "which commit built this image?".

MIT licensed · Built for internal networks · Single-file SQLite storage

Jenkins Build Archive build list: stat cards, search filters and the build record table

Build list (demo data): search by job, branch, commit or image tag; today only by default.

An archive that only keeps the record

Jenkins cleans up old builds to save space, but when you debug a production issue you need to know which commit an image came from. This keeps that information in a small separate service.

WhatA build metadata archive independent of Jenkins; builds stay traceable after their records are cleaned up
RecordsJob, build number, Git repository / branch / commit, commit author and changed files, Docker image tag and digest, build result and duration
IngestThe pipeline posts at the end through the Shared Library's buildArchive() or a single curl; (jobName, buildId) is idempotent
QueryBuilt-in web UI and REST API, with search by job / branch / commit / tag and date filters
StackPython + FastAPI + SQLite, deployed with Docker Compose, MIT licensed

Small, sufficient, out of the way

Build records that last

After Jenkins removes a Build Record, the branch, commit and image information is still in the archive.

Git and commit details

Branch, commit SHA, message, author and changed files are all recorded and shown on one detail page.

Image tag and digest

A tag can be overwritten, a digest cannot. Recording both is what makes long-term tracing reliable.

Search and filters

Filter by job, branch, commit, image tag, result and date. The list shows today by default, and "All history" is one click away.

One line with the Shared Library

Call buildArchive() in post { always { } }. If you would rather skip the Shared Library, a plain curl works too.

Archive failures never fail the build

An error in the archive call only prints a warning and never turns the pipeline red; both successful and failed builds are stored.

Retention policy and bulk delete

Set a maximum age and a per-job record cap. Deleting needs the admin password and a typed confirmation word.

Auth for writes and admin

Write APIs use API_TOKEN and web admin uses ADMIN_PASSWORD; if either is not configured, those operations are refused.

One container, little upkeep

One container and one SQLite file, started with Docker Compose. The schema initializes itself, and upgrading is just a rebuild.

Three steps

Deploy the archive

Start it with Docker Compose on an internal server and set API_TOKEN and the admin password.

Configure Jenkins

Set BUILD_ARCHIVE_URL and the token, and register this repo as a Shared Library named build-archive.

Call it in the pipeline

Call buildArchive() once in post { always { } }, and every build is stored automatically from then on.

Integrate with Jenkins

The Shared Library is recommended; you can also skip it and curl straight from the Jenkinsfile.

Shared Library (recommended)

First, under Manage Jenkins, set the global environment variables BUILD_ARCHIVE_URL and BUILD_ARCHIVE_TOKEN, and register this repo as a Global Pipeline Library named build-archive. Then:

@Library('build-archive') _

pipeline {
    agent any
    stages {
        stage('Build') {
            steps { sh 'echo build...' }
        }
    }
    post {
        always {
            buildArchive()   // must be always, so failed builds are stored too
        }
    }
}

The agent needs curl installed.

Include the image tag and digest

Before calling buildArchive(), put the image information in environment variables:

stage('Push image') {
    steps {
        script {
            def tag = "registry.example.com/dev/my-app:${env.BUILD_NUMBER}"
            env.DOCKER_IMAGE_TAG = tag
            sh "docker build -t ${tag} . && docker push ${tag}"
            env.DOCKER_IMAGE_DIGEST = sh(
                script: "docker inspect --format='{{index .RepoDigests 0}}' ${tag} | awk -F@ '{print \$2}'",
                returnStdout: true
            ).trim()
        }
    }
}

The service never queries the registry itself; the digest is passed in by the pipeline.

Without the Shared Library: plain POST

The ingest endpoint is POST /api/v1/builds, authenticated with a Bearer token; the same (jobName, buildId) updates the existing record. Try it by hand first:

curl -X POST http://127.0.0.1:8080/api/v1/builds \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -d '{"jobName":"demo","buildId":1,"buildDate":"2026-08-14T10:00:00+08:00","buildResult":"SUCCESS","buildUrl":"https://jenkins.example/job/demo/1/"}'
Build detail page: build info, Git info, commit details and Docker image
Build detail (demo data): Git, commit, image tag and digest on one page
Archive fieldJenkins source
jobNameJOB_NAME
buildIdBUILD_NUMBER
gitBranchGIT_BRANCH / BRANCH_NAME
gitCommitGIT_COMMIT
commitMsg / commitAuthor / commitFilesSame-named environment variables, or buildArchive(...) arguments
dockerImageTag / dockerImageDigestDOCKER_IMAGE_TAG / DOCKER_IMAGE_DIGEST
buildResultcurrentBuild.currentResult
buildUrl / durationMsBUILD_URL / currentBuild.duration

REST API

Read endpoints are open by default; write and admin endpoints require a Bearer token.

MethodPathAuthDescription
GET/healthNoneHealth check
GET/api/v1/statsNoneStatistics
GET/api/v1/buildsNoneList, supports q / job / branch / result / dateFrom / dateTo / page / pageSize
GET/api/v1/builds/{id}NoneDetail (including commit info)
POST/api/v1/buildsBearerCreate or update (idempotent)
DELETE/api/v1/builds/{id}BearerDelete
GET/api/v1/admin/retentionBearerView the retention policy
POST/api/v1/admin/retention/runBearerRun the cleanup now

Note: the API list applies no date filter by default; the web list shows only today, and /?all=1 shows the full history.

Quick deploy

You need an internal server with Docker and Docker Compose. All data lives in a single SQLite file.

  1. Get the code

    Clone the repo, enter the directory and copy the environment template.

    git clone https://github.com/AlexWuYh/jenkins-build-archive.git
    cd jenkins-build-archive
    cp .env.example .env
  2. Edit the config

    Replace API_TOKEN and ADMIN_PASSWORD with your own values; do not keep the placeholders from the example.

    API_TOKEN=replace-with-a-long-random-string
    ADMIN_PASSWORD=set-an-admin-password
    TZ=Asia/Shanghai
    RETENTION_DAYS=365
    RETENTION_MAX_PER_JOB=0
  3. Start and check

    Once started, call the health endpoint, then open http://SERVER_IP:8080 in a browser.

    docker compose up -d --build
    curl http://127.0.0.1:8080/health
  4. Backup and upgrade

    Data lives in ./data/build_archive.db. Use SQLite's backup API for online backups; to upgrade, pull the new code and rebuild, and the schema initializes itself.

    docker exec jenkins-build-archive python -c \
      "import sqlite3; s=sqlite3.connect('/data/build_archive.db'); d=sqlite3.connect('/data/backup.db'); s.backup(d); d.close(); s.close()"
    
    git pull
    docker compose up -d --build
VariableMeaning
API_TOKENBearer token for the write API; BUILD_ARCHIVE_TOKEN on the Jenkins side must match it
ADMIN_PASSWORDPassword for web admin (delete, retention policy)
TZTime zone; decides what "today" means in the list
RETENTION_DAYSMaximum retention in days, 0 turns it off; once saved in the UI, the value in the database wins
RETENTION_MAX_PER_JOBMaximum records kept per job, 0 turns it off

Internal networks only: read endpoints need no login by default, so do not expose the service to the public internet, or restrict sources at the reverse proxy. Cleanup cannot be undone; back up the database before tightening the policy.

Full documentation (currently in Chinese): GitHub README, Shared Library docs.

Make every build traceable

Free, open source, and running inside your own network.