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
| Feature | Gitness / Harness OS | Gitea | Forgejo | GitLab CE | OneDev |
|---|---|---|---|---|---|
| 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:
- Create your admin account — the first user registered becomes the admin
- Create a project — Gitness organizes repositories under projects (like GitHub organizations)
- 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:
- Navigate to your project → Secrets
- Click New Secret
- 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:
- Go to your repository → Settings → Rules
- Create a new rule targeting
main - 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
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.sockfor 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.yamlextension. - 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: pipelinewrapper structure is slightly different - Plugin syntax uses
type: pluginwithinputsinstead ofsettings
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:
- Caddy reverse proxy setup for easy HTTPS
- Docker volume management for backup strategies
- Woodpecker CI if you prefer a dedicated CI tool
- Kopia backup for encrypted offsite backups
- Authentik SSO for single sign-on across your services