Open-Source Wikis

/

GitLab

/

Apps

/

Workhorse

gitlab-org/gitlab

Workhorse

A Go reverse proxy that intercepts and accelerates resource-intensive HTTP traffic before it reaches Puma. Lives under workhorse/ in this repo and ships as a separate binary gitlab-workhorse.

Purpose

Puma is fast for HTML and API requests but bad for streaming large files, hijacking websockets, or holding connections open for git-over-HTTP transfers. Workhorse handles those paths so Puma never sees them.

Specifically Workhorse handles:

  • File uploads (LFS, packages, artifacts, attachments) — accepts the file, writes to object storage, then signals Rails with a tiny JSON metadata POST.
  • File downloads (artifacts, packages, LFS) — gets a signed URL from Rails and streams from object storage directly.
  • Git operations over HTTP (git push/git pull) — proxies info/refs, git-upload-pack, git-receive-pack to Gitaly via gRPC.
  • Repository archive downloads (/-/archive/...) — cached and streamed.
  • Image resizing (avatars, repository file previews).
  • Web sockets for the in-IDE terminal, build trace tail, and Action Cable.
  • Static asset serving and prefetch caching.
  • Dependency-proxy traffic.
  • AI assistant traffic (internal/ai_assist/duoworkflow/) — proxies to the Duo Workflow service.

Directory layout

workhorse/
├── cmd/                       # Executable entry points
│   ├── gitlab-workhorse/      # Main reverse proxy binary
│   ├── gitlab-resize-image/   # Image resizing helper
│   ├── gitlab-zip-cat/        # Stream a single zip entry
│   └── gitlab-zip-metadata/   # Read zip TOC
├── internal/                  # Go packages, not exported
│   ├── upstream/              # HTTP routing to Rails / object storage
│   ├── upload/                # Handles streamed uploads
│   ├── git/                   # Git over HTTP
│   ├── gitaly/                # gRPC client to Gitaly
│   ├── senddata/              # X-SendData header parsing
│   ├── sendfile/, sendurl/    # Streaming response helpers
│   ├── artifacts/             # CI artifact handling
│   ├── lsif_transformer/      # LSIF code-intel transformer
│   ├── zipartifacts/          # Zip-aware artifact APIs
│   ├── imageresizer/          # Image resize transforms
│   ├── dependencyproxy/       # Pull-through cache for upstreams
│   ├── ai_assist/duoworkflow/ # Proxy to Duo Workflow service
│   ├── circuitbreaker/, queueing/, bodylimit/, rejectmethods/  # guardrails
│   ├── api/                   # Comm with Rails (auth tokens, "preauth")
│   ├── config/                # Workhorse config parsing
│   ├── log/, metrics/         # Structured logging, Prometheus metrics
│   └── ...                    # Helpers, headers, transport, redis, etc.
├── _support/                  # Build/test helper scripts
├── testdata/                  # Fixtures
├── Makefile                   # `make`, `make test`, `make lint`
├── go.mod                     # Go module
└── README.md

How a request is handled

sequenceDiagram
    participant Client
    participant WH as Workhorse (cmd/gitlab-workhorse)
    participant Rails as Puma / Rails
    participant OS as Object storage
    participant Gitaly

    Note over Client,Rails: 1. POST /-/upload (artifact)
    Client->>WH: POST multipart body
    WH->>Rails: HTTP POST /authorize (no body)
    Rails-->>WH: signed object-storage URL + JWT
    WH->>OS: PUT body
    OS-->>WH: 200 OK + ETag
    WH->>Rails: POST /finalize { object_path, etag, size }
    Rails-->>WH: 200 OK
    WH-->>Client: 200 OK

    Note over Client,Gitaly: 2. git fetch
    Client->>WH: GET info/refs?service=git-upload-pack
    WH->>Rails: HTTP /authorize (Auth check)
    Rails-->>WH: gitaly addr + repo + token
    WH->>Gitaly: gRPC InfoRefsUploadPack
    Gitaly-->>WH: streamed packets
    WH-->>Client: streamed packets

The "preauthorize" step sees Rails return small JSON responses describing where the heavy work should land, then Workhorse executes that work without bothering Rails again.

Headers Workhorse understands

Header Direction Purpose
Gitlab-Workhorse-Send-Data Rails → WH Tells WH to fetch the body from elsewhere (object storage, zip entry, file)
Gitlab-Workhorse-Detect-Content-Type Rails → WH Run file(1)-style sniffer before sending
Gitlab-Workhorse-Buffering Rails → WH Disable response buffering for SSE
Gitlab-Workhorse-Multipart-Fields WH → Rails Names of fields that contained uploaded files
Gitlab-Workhorse-Accelerated-Redirect Rails → WH Skip Puma for this internal redirect

internal/senddata/, internal/sendurl/, and internal/sendfile/ parse these.

Build and test

cd workhorse
make             # build all binaries
make test        # run unit tests
make test-race   # tests with -race
make lint        # golangci-lint
make fmt         # goimports

CI runs the same targets; lint config is in workhorse/.golangci.yml. Test data lives in workhorse/testdata/.

Configuration

A typical config file (see workhorse/config.toml.example):

[redis]
url = "unix:///var/opt/gitlab/redis/redis.socket"

[object_storage]
provider = "AWS"

[image_resizer]
max_scaler_procs = 4
max_filesize = 250000

Workhorse picks up its config via --config flag, environment variables, or sensible defaults.

Where to make changes

  • New upload type: internal/upload/ plus a corresponding Rails endpoint that issues a "preauthorize" payload.
  • New send-data type: register in internal/senddata/ and emit the header from Rails.
  • New gRPC call to Gitaly: extend internal/gitaly/.
  • Performance hot path: profile with pprof; internal/log/ already wires this up.
  • Internal API — the small "preauthorize" endpoints Rails exposes for Workhorse.
  • Architecture — where Workhorse sits in the request flow.
  • Tooling — golangci-lint config.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Workhorse – GitLab wiki | Factory