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) — proxiesinfo/refs,git-upload-pack,git-receive-packto 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.mdHow 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 packetsThe "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 # goimportsCI 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 = 250000Workhorse 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.
Related
- 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.