Self-Hosting Gitness (Harness Open Source): Modern Git Hosting with Built-In CI/CD

Most self-hosted Git solutions make you bolt on CI/CD as an afterthought. You set up Gitea, then add Woodpecker or Drone, configure webhooks, manage two sets of credentials, and pray the integration doesn’t break during updates.

Gitness — now officially Harness Open Source — takes a different approach. It’s a single binary that ships Git hosting, CI/CD pipelines, code review, secret scanning, and cloud developer environments in one package. Born as the next generation of Drone CI, it inherits Drone’s container-native pipeline philosophy while adding a complete source control layer on top.

If you want a lightweight, all-in-one DevOps platform that doesn’t require stitching together five different tools, Gitness is worth a serious look.

Gitness vs Other Self-Hosted Git Platforms

FeatureGitness / Harness OSGiteaForgejoGitLab CEOneDev
Open source✅ Apache 2.0✅ MIT✅ GPLv3+✅ MIT (CE)✅ MIT
Built-in CI/CD✅ Native pipelines❌ (Actions beta)❌ (Actions beta)✅ GitLab CI✅ Built-in
Secret scanning✅ Built-in✅ (Ultimate)
Code review✅ Pull requests✅ Pull requests✅ Pull requests✅ Merge requests✅ Pull requests
Dev environments✅ Gitspaces✅ Web IDE
Artifact registry✅ Docker, Helm✅ Packages✅ Packages✅ Container registry
Resource usage~200MB RAM~150MB RAM~150MB RAM~4GB RAM~1GB RAM
Migration tools✅ One-click✅ Built-in✅ Built-in✅ Built-in✅ Built-in
Single binary❌ (multi-service)

Gitness hits a sweet spot: more integrated than Gitea (which needs external CI), vastly lighter than GitLab (which needs a dedicated server), and more polished than OneDev for teams coming from GitHub or Drone.

Prerequisites

  • A Linux server (VPS, home server, or VM) with at least 1 CPU core and 512MB RAM
  • Docker and Docker Compose installed (Docker setup guide)
  • A domain name (optional but recommended for HTTPS)
  • 10 minutes of your time

Quick Start with Docker

The fastest way to get Gitness running is a single Docker command:

docker run -d \
  -p 3000:3000 \
  -p 3022:3022 \
  -v harness_data:/data \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --name harness \
  --restart always \
  harness/harness:3.3.0

That’s it. Visit http://your-server:3000 and create your admin account.

But for production use, let’s do this properly with Docker Compose.

Production Docker Compose Setup

Create your project directory:

mkdir -p ~/gitness && cd ~/gitness

Create a docker-compose.yml:

services:
  harness:
    image: harness/harness:3.3.0
    container_name: harness
    restart: unless-stopped
    ports:
      - "3000:3000"   # Web UI + HTTP Git
      - "3022:3022"   # SSH Git
    volumes:
      - harness_data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # Instance configuration
      - GITNESS_URL_BASE=https://git.yourdomain.com
      - GITNESS_HTTP_PORT=3000
      - GITNESS_SSH_PORT=3022
      - GITNESS_ENCRYPTER_SECRET=${ENCRYPTER_SECRET}
      # User registration
      - GITNESS_USER_SIGNUP_ENABLED=false
      # CI/CD runner configuration
      - GITNESS_CI_DOCKER_HOST=unix:///var/run/docker.sock
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

volumes:
  harness_data:
    name: harness_data

Generate your encryption secret:

echo "ENCRYPTER_SECRET=$(openssl rand -hex 16)" > .env

Start the stack:

docker compose up -d

Gitness uses an embedded SQLite database and stores everything under /data — repositories, database, configuration. One volume, one container, zero complexity.

Initial Setup

Navigate to http://your-server:3000 (or your domain). You’ll be greeted with a registration page:

  1. Create your admin account — the first user registered becomes the admin
  2. Create a project — Gitness organizes repositories under projects (like GitHub organizations)
  3. Create your first repository — or import one from GitHub, GitLab, or Bitbucket

Importing Repositories

Gitness includes one-click migration from major platforms. Go to your project, click Import Repository, and select the source:

  • GitHub — paste the repo URL, optionally add a personal access token for private repos
  • GitLab — same process, supports groups
  • Bitbucket — supports both Cloud and Server

The import brings over branches, tags, and commit history. Pull requests and issues don’t transfer (this is a Git-level import, not a full platform migration).

SSH Key Setup

For SSH Git access on port 3022:

# Add your public key in the web UI under User Settings > SSH Keys
# Then clone via SSH:
git clone ssh://git@your-server:3022/your-project/your-repo.git

Setting Up CI/CD Pipelines

This is where Gitness really differentiates itself. Pipelines are defined in .harness/ YAML files in your repository — similar to GitHub Actions or Drone.

Your First Pipeline

Create .harness/build.yaml in your repository:

kind: pipeline
spec:
  stages:
    - type: ci
      spec:
        steps:
          - name: test
            type: run
            spec:
              container: node:20-alpine
              script: |
                npm ci
                npm test

          - name: lint
            type: run
            spec:
              container: node:20-alpine
              script: |
                npm ci
                npm run lint

Commit and push this file. Gitness automatically detects the pipeline and runs it on the next push.

Pipeline with Services

Need a database for integration tests? Add services:

kind: pipeline
spec:
  stages:
    - type: ci
      spec:
        steps:
          - name: integration-tests
            type: run
            spec:
              container: python:3.12
              script: |
                pip install -r requirements.txt
                pytest tests/integration/
              envs:
                DATABASE_URL: postgres://postgres:testpass@database:5432/testdb

        platform:
          os: linux
          arch: amd64

  services:
    - name: database
      image: postgres:16-alpine
      envs:
        POSTGRES_PASSWORD: testpass
        POSTGRES_DB: testdb

Build and Push Docker Images

kind: pipeline
spec:
  stages:
    - type: ci
      spec:
        steps:
          - name: build-and-push
            type: plugin
            spec:
              name: docker
              inputs:
                repo: registry.yourdomain.com/myapp
                tags: latest,${DRONE_COMMIT_SHA:0:8}
                username:
                  from_secret: registry_user
                password:
                  from_secret: registry_pass

Managing Secrets

Add secrets through the web UI at the project or repository level:

  1. Navigate to your project → Secrets
  2. Click New Secret
  3. Enter the name and value

Reference secrets in pipelines with from_secret: secret_name. Secrets are encrypted at rest using your ENCRYPTER_SECRET.

Pipeline Triggers

By default, pipelines run on every push. Customize triggers:

kind: pipeline
spec:
  triggers:
    - type: push
      spec:
        branch: main
    - type: pull_request
      spec:
        events:
          - opened
          - synchronized

Code Review and Branch Protection

Setting Up Branch Rules

Protect your main branch:

  1. Go to your repository → SettingsRules
  2. Create a new rule targeting main
  3. Configure:
    • Require pull request — no direct pushes
    • Require approval — minimum number of reviewers
    • Require status checks — pipelines must pass
    • Block force push — prevent history rewrites

Pull Request Workflow

Gitness pull requests support:

  • Line-by-line code comments with threading
  • Suggested changes that can be committed directly
  • Required reviewers and approval rules
  • Automatic pipeline triggers on PR events
  • Merge strategies — merge commit, squash, or rebase

Reverse Proxy Configuration

Caddy

g}it.yroeuvredrosmea_ipnr.ocxoyml{ocalhost:3000

Caddy handles HTTPS automatically. That’s the entire config.

Nginx

server {
    listen 443 ssl http2;
    server_name git.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/git.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.yourdomain.com/privkey.pem;

    client_max_body_size 512m;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Required for Git operations
        proxy_buffering off;
        proxy_request_buffering off;
    }
}

After setting up your reverse proxy, update GITNESS_URL_BASE to match your domain.

Backup and Restore

Everything lives in one volume, making backups straightforward:

Backup

# Stop the container for a consistent backup
docker compose stop harness

# Backup the data volume
docker run --rm \
  -v harness_data:/data \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/harness-$(date +%Y%m%d).tar.gz -C /data .

# Restart
docker compose start harness

Restore

docker compose stop harness

# Clear existing data
docker volume rm harness_data
docker volume create harness_data

# Restore from backup
docker run --rm \
  -v harness_data:/data \
  -v $(pwd)/backups:/backup \
  alpine tar xzf /backup/harness-20260322.tar.gz -C /data

docker compose start harness

Automated Backup Script

#!/bin/bash
BACKUP_DIR="$HOME/backups/harness"
RETENTION_DAYS=30
mkdir -p "$BACKUP_DIR"

docker compose -f ~/gitness/docker-compose.yml stop harness
docker run --rm \
  -v harness_data:/data \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf "/backup/harness-$(date +%Y%m%d-%H%M%S).tar.gz" -C /data .
docker compose -f ~/gitness/docker-compose.yml start harness

# Prune old backups
find "$BACKUP_DIR" -name "harness-*.tar.gz" -mtime +"$RETENTION_DAYS" -delete

Add to cron for daily backups: 0 3 * * * /home/user/scripts/backup-harness.sh

Troubleshooting

Container Fails to Start

Check logs first:

docker logs harness

Common issues:

  • Port conflict — something else is using 3000 or 3022. Change the host ports in your compose file.
  • Docker socket permissions — the container needs access to /var/run/docker.sock for CI/CD. On some systems, you may need to adjust the socket permissions or add the container user to the docker group.

Pipelines Not Running

  • Docker socket not mounted — CI/CD requires Docker socket access. Verify the volume mount in your compose file.
  • Pipeline file not detected — files must be in .harness/ directory with .yaml extension.
  • Trigger mismatch — check your trigger configuration matches the event (push vs PR).

Git Push Fails with Large Files

Increase the proxy buffer size if behind Nginx:

client_max_body_size 512m;
proxy_buffering off;
proxy_request_buffering off;

For large repositories, consider Git LFS which Gitness supports natively.

High Memory Usage During CI

Pipeline containers are separate from the main Gitness container. If CI builds are consuming too much memory, limit them in your pipeline:

steps:
  - name: build
    type: run
    spec:
      container: node:20
      resources:
        limits:
          memory: 512m

Migration from Drone

If you’re coming from Drone, the pipeline syntax is similar but not identical. Key differences:

  • Pipeline files go in .harness/ instead of .drone.yml
  • The kind: pipeline wrapper structure is slightly different
  • Plugin syntax uses type: plugin with inputs instead of settings

Harness includes a migration guide at their docs site, and they plan eventual full parity with Drone’s pipeline capabilities.

Power User Tips

One-click repository mirroring — set up mirrors to keep a backup of your repos on GitHub or GitLab automatically.

Webhook integrations — configure webhooks at the project or repo level to notify external services (Slack, Discord, deployment scripts) on push, PR, or pipeline events.

API access — Gitness exposes a REST API for automation. Generate a personal access token in User Settings and use it for programmatic access:

curl -H "Authorization: Bearer your-token" \
  https://git.yourdomain.com/api/v1/user/repos

Resource limits — for shared servers, limit Gitness memory:

services:
  harness:
    # ... existing config
    deploy:
      resources:
        limits:
          memory: 1G

Multiple projects for team organization — use projects like GitHub organizations. Each project can have its own secrets, members, and access controls. Great for separating personal projects from team work.

Artifact registry — Gitness includes a built-in Docker registry and Helm chart repository. Push images from your CI pipelines without needing a separate registry service.

Updating Gitness

cd ~/gitness
docker compose pull
docker compose up -d

Always backup before updating. Check the release notes for breaking changes between versions.

Conclusion

Gitness (Harness Open Source) is the rare self-hosted tool that genuinely simplifies your stack instead of adding to it. Instead of maintaining Gitea + Woodpecker + a Docker registry + webhook glue, you get one container that handles all of it.

The trade-off is ecosystem maturity — Gitea and Forgejo have larger plugin ecosystems and more community extensions. GitLab has vastly more features (if you can spare the RAM). But if you want a clean, modern, all-in-one Git + CI/CD platform that starts at 200MB of RAM, Gitness is hard to beat.

For small teams and solo developers who want CI/CD without the infrastructure overhead, it’s an excellent choice.

Related guides: