Docker installation: start gitrust in 30 minutes with docker-compose

Goals

At the end of this tutorial, you will know:

  • O1. Deploy the minimal gitrust stack (application + PostgreSQL) with docker-compose
  • O2. Verify that all three services start correctly and are healthy
  • O3. Create the first administrator account and access the web interface

Prerequisites

  • Technical: a VM or Linux machine (Debian 12 / Ubuntu 22.04 or higher), Docker Engine ≥ 24 installed (docker --version responds), Docker Compose plugin installed (docker compose version responds), ports 4000 and 2222 free on the machine
  • Image Docker gitrust : à construire localement à partir du code source (l'image n'est pas encore publiée sur un registre public). Suivez la section Construire l'image gitrust ci-dessous avant le docker compose up.
  • Educational: no prior gitrust tutorial required — this is the starting point of the administrator journey
  • Estimated time: ~30 minutes

Construire l'image gitrust

Image non encore publiée

gitrust fournit un Dockerfile multi-stage (Rust + Tailwind/DaisyUI) dans le dépôt source, mais aucune image officielle n'est encore publiée sur un registre public (Docker Hub, ghcr.io, quay.io). L'installation Docker officielle est donc expérimentale : il faut cloner le dépôt et construire l'image localement. Le support Docker production sera stabilisé dans une prochaine release.

git clone https://demo.gitrust.eu/gitrust/gitrust.git
cd gitrust
docker build -t gitrust:latest .

À la fin, docker image ls | grep gitrust doit afficher l'image locale gitrust:latest. Le build prend ~8-15 min (compilation Rust en release + build CSS Tailwind). Si vous rencontrez une erreur, ouvrez une issue sur le dépôt source — le pipeline Docker n'a pas encore été testé en production.

Overview

Before launching any command, let's understand what docker-compose will orchestrate and why.

gitrust is composed of three main services in its minimum configuration. Think of it like three rooms in an apartment calling each other via an internal network:

graph TB
    subgraph internet ["Internet / votre navigateur"]
        B[Navigateur :4000]
        S[Client git/SSH :2222]
    end

    subgraph docker ["Réseau Docker gitrust_net"]
        A["gitrust-app
(Rust, port 4000 + 2222)
Lit la config depuis .env"] P["postgres
(PostgreSQL 16, port 5432)
Stocke comptes, dépôts, issues"] R["redis (optionnel)
(Redis 7, port 6379)
Sessions web + cache"] end B -->|HTTP| A S -->|SSH| A A -->|SQL| P A -.->|sessions| R

gitrust-app is the core: HTTP server (:4000) + SSH server (:2222) compiled in Rust. It reads its configuration from an .env file and writes bare Git repositories to a Docker volume. PostgreSQL stores all relational data (accounts, organizations, repositories, issues, PRs). Redis is optional in the minimum configuration — it manages web sessions and cache when present.

Dans ce tutoriel, nous utilisons la stack minimale (app + PostgreSQL) suffisante pour évaluer gitrust et démarrer une petite instance. La stack production avec Redis et SMTP se trouve dans template/docker/docker-compose.production.yml.


Step 1: Create the working directory and configuration file

Create a directory dedicated to your gitrust instance and position yourself in it:

mkdir -p /opt/gitrust && cd /opt/gitrust

Create the .env configuration file with the minimum values. Must change the values ​​marked CHANGE_ME:

cat > .env << 'EOF'
# === gitrust — configuration minimale ===
# Changez toutes les valeurs CHANGE_ME avant de démarrer

# Clé secrète de l'application (générez avec : openssl rand -hex 32)
SECRET_KEY=CHANGE_ME_openssl_rand_hex_32

# URL publique de l'instance (sans slash final)
APP_URL=http://localhost:4000

# Base de données PostgreSQL
DATABASE_URL=postgres://gitrust:CHANGE_ME_db_password@postgres:5432/gitrust
POSTGRES_USER=gitrust
POSTGRES_PASSWORD=CHANGE_ME_db_password
POSTGRES_DB=gitrust

# Serveur SSH (port exposé à l'extérieur)
SSH_PORT=2222

# Mode de l'application
RUST_LOG=info
APP_ENV=production
EOF

Now generate a robust secret key and replace the value in .env:

SECRET=$(openssl rand -hex 32)
sed -i "s/CHANGE_ME_openssl_rand_hex_32/$SECRET/" .env

Also choose a password for PostgreSQL and replace both occurrences:

DB_PASS=$(openssl rand -hex 16)
sed -i "s/CHANGE_ME_db_password/$DB_PASS/g" .env

Expected output (verification):

grep SECRET_KEY .env
# → SECRET_KEY=4a7f2c9e1b8d3f6a0e5c2b9d7f4a1e8c3b6d9f2a5e8c1b4d7f0a3e6c9b2d5f8a

Checkpoint: run grep CHANGE_ME .env — the command should return nothing. If it returns rows, these values ​​have not been replaced and the startup will fail.


Step 2: Create the docker-compose.yml file

Create the following docker-compose.yml file in /opt/gitrust/. This content corresponds to the gitrust minimum stack — it is reproduced here in full so you can get started without external dependencies:

cat > docker-compose.yml << 'EOF'
# docker-compose.minimal.yml — Stack gitrust minimale (app + PostgreSQL)
# Cas d'usage : évaluation, développement, petite instance (<10 utilisateurs)
# Pour la production : utilisez docker-compose.production.yml (+ Redis + SMTP)
#
# Usage :
#   docker compose up -d
#   docker compose logs -f gitrust
#   docker compose down

version: "3.9"

services:
  # ── Base de données ───────────────────────────────────────────────────────
  postgres:
    image: postgres:16-alpine
    container_name: gitrust_postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      # Données persistantes — ne supprimez pas ce volume sans sauvegarde
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - gitrust_net

  # ── Application gitrust ───────────────────────────────────────────────────
  gitrust:
    image: gitrust:latest
    container_name: gitrust_app
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    env_file:
      # Lit toutes les variables depuis .env — ne commitez jamais ce fichier
      - .env
    ports:
      # Interface web — exposée sur toutes les interfaces de la machine hôte
      - "4000:4000"
      # Serveur SSH Git — exposé sur le port configuré dans .env
      - "${SSH_PORT:-2222}:2222"
    volumes:
      # Dépôts Git bare — données critiques, sauvegardez ce volume
      - git_repos:/var/lib/gitrust/repositories
      # Clés SSH du serveur — générées au premier démarrage, persistez-les
      - ssh_host_keys:/etc/gitrust/ssh
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
    networks:
      - gitrust_net

volumes:
  # postgres_data : données relationnelles (comptes, dépôts, issues, PRs)
  postgres_data:
  # git_repos : dépôts Git bare (le contenu réel des commits)
  git_repos:
  # ssh_host_keys : clés SSH hôte (fingerprint de votre serveur)
  ssh_host_keys:

networks:
  gitrust_net:
    driver: bridge
EOF

Checkpoint: check that both files are present in /opt/gitrust/:

ls -la /opt/gitrust/

Expected output:

total 16
drwxr-xr-x 2 root root 4096 avr 17 10:00 .
drwxr-xr-x 8 root root 4096 avr 17 09:55 ..
-rw------- 1 root root  612 avr 17 10:00 .env
-rw-r--r-- 1 root root 2048 avr 17 10:00 docker-compose.yml


Step 3: Start the stack and follow the logs

Start all background services:

docker compose up -d

Expected output:

[+] Running 4/4
 ✔ Network gitrust_gitrust_net    Created                          0.1s
 ✔ Volume "gitrust_postgres_data" Created                          0.0s
 ✔ Container gitrust_postgres     Started                          0.8s
 ✔ Container gitrust_app          Started                          1.2s

Follow the application logs to observe the startup (migrations run automatically):

docker compose logs -f gitrust

Expected output (first 15-20 seconds):

gitrust_app  | 2026-04-17T10:00:15Z INFO  gitrust > Starting gitrust v0.9.0
gitrust_app  | 2026-04-17T10:00:15Z INFO  gitrust > Connecting to database...
gitrust_app  | 2026-04-17T10:00:16Z INFO  gitrust > Running database migrations...
gitrust_app  | 2026-04-17T10:00:17Z INFO  gitrust > Applied 42 migrations successfully
gitrust_app  | 2026-04-17T10:00:17Z INFO  gitrust > Generating SSH host keys...
gitrust_app  | 2026-04-17T10:00:17Z INFO  gitrust > SSH host key fingerprint: SHA256:xxxxxxxxxxxxxxxxxxxx
gitrust_app  | 2026-04-17T10:00:17Z INFO  gitrust > HTTP server listening on 0.0.0.0:4000
gitrust_app  | 2026-04-17T10:00:17Z INFO  gitrust > SSH server listening on 0.0.0.0:2222
gitrust_app  | 2026-04-17T10:00:17Z INFO  gitrust > gitrust is ready

Press Ctrl+C to exit log monitoring (containers continue to run).

Checkpoint: check the health status of the services:

docker compose ps

Expected output:

NAME              IMAGE                          COMMAND                  SERVICE    CREATED         STATUS                   PORTS
gitrust_app       gitrust:latest "/usr/local/bin/gitr…"  gitrust    2 minutes ago   Up 2 minutes (healthy)   0.0.0.0:4000->4000/tcp, 0.0.0.0:2222->2222/tcp
gitrust_postgres  postgres:16-alpine             "docker-entrypoint.s…"  postgres   2 minutes ago   Up 2 minutes (healthy)   5432/tcp

Both containers should display (healthy). If one displays (starting), wait 30 seconds and retry the command. If it shows (unhealthy), check the logs with docker compose logs gitrust or docker compose logs postgres.


Step 4: Create the first administrator account

Open your browser and go to http://localhost:4000.

You should see the gitrust homepage with a registration form. As the instance is blank, the first account created automatically becomes administrator.

Remplissez le formulaire : - Pseudo: your administrator identifier (e.g. admin) - Email: your email address - Password: choose a strong password (≥ 12 characters)

Click on Create my account.

Expected output in the logs (visible with docker compose logs -f gitrust):

gitrust_app  | 2026-04-17T10:05:00Z INFO  gitrust::auth > New user registered: admin (id=1)
gitrust_app  | 2026-04-17T10:05:00Z INFO  gitrust::admin > First user promoted to administrator: admin

Final checkpoint: log in with your credentials. After logging in, verify that the top right menu shows an Administration link (or navigate to http://localhost:4000/admin). This page is only accessible to administrators — its presence confirms that your account has the admin role.

Your instance URL is http://localhost:4000 and your administrator account is operational.


Summary

  • O1 accomplished: the docker-compose stack is deployed — docker compose ps displays two (healthy) containers
  • O2 accomplished: the three services (app, PostgreSQL, internal network) start correctly — the logs show “gitrust is ready” and the migrations are applied
  • O3 accomplished: the first administrator account is created and the /admin page is accessible

And if it doesn't work

Symptôme Cause probable Correction
Error: port 4000 is already in use au démarrage Un autre service occupe le port 4000 sur la machine hôte Identifiez le service avec ss -tlnp \| grep 4000 ou lsof -i :4000. Arrêtez-le ou changez le port dans docker-compose.yml : remplacez "4000:4000" par "4001:4000" et accédez sur :4001
Le container gitrust_app affiche (unhealthy) ou redémarre en boucle La variable DATABASE_URL est incorrecte ou SECRET_KEY contient des caractères spéciaux non échappés Vérifiez avec docker compose logs gitrust \| grep ERROR. Assurez-vous que SECRET_KEY est bien une chaîne hexadécimale sans guillemets. Rechargez avec docker compose down && docker compose up -d
La page http://localhost:4000 affiche « Connection refused » Le container n'est pas encore en (healthy) ou le port n'est pas exposé Attendez 30 secondes et rechargez. Si le problème persiste, vérifiez que votre pare-feu local (ufw status) autorise le port 4000
FATAL: password authentication failed for user "gitrust" dans les logs postgres Le mot de passe dans DATABASE_URL ne correspond pas à POSTGRES_PASSWORD Supprimez le volume PostgreSQL (docker compose down -v) et relancez — attention : supprime toutes les données

Next step

02 — Systemd installation: native binary + service: discover the installation without Docker for a production deployment with AppArmor hardening (~45 min)

Or if you want to make this instance accessible from the Internet:

04 — Put into production: reverse-proxy TLS, SMTP, automatic backup (~90 min)