Skip to content

Getting started

Self-host Cloudzilla

Cloudzilla is a single static binary backed by PostgreSQL. There is no git executable to install, no message queue, and no separate search service — this guide takes you from a fresh host to a running forge.

Introduction

Everything Cloudzilla needs at runtime is the binary itself and a PostgreSQL database. Git transport (smart HTTP and SSH) is implemented in pure Go via go-git, full-text search runs on Postgres tsvector, and the UI is server-rendered HTML over the wire. That means a deployment is two moving parts you already know how to operate.

In a hurry? Skip to Installation — Docker, a CZ_DATABASE_DSN, and one migrate command get you a forge on port 8080.

What you'll need

  • A Linux host (amd64 or arm64), or Docker on any platform.
  • PostgreSQL 14+ — managed or self-run; it's the only runtime dependency.
  • Two open ports: one for HTTP (default 8080) and one for SSH Git access (default 2222).

Installation

Pick whichever fits your stack. Each ships the same server binary plus a small bundled admin CLI (cloudzilla-cli) that runs database migrations — Docker is the fastest path to a running instance.

Docker

Pull the alpha image and point it at a database. The schema isn't applied automatically — run cloudzilla-cli migrate once (it's bundled in the image), and the database only needs to exist and be reachable.

docker
$ docker run -d --name cloudzilla \
    -p 8080:8080 -p 2222:2222 \
    -v cloudzilla_data:/data \
    -e CZ_DATABASE_DSN=postgres://cz:secret@db:5432/cloudzilla?sslmode=disable \
    -e CZ_SERVER_BASE_URL=https://git.example.com \
    -e CZ_AUTH_JWT_SECRET=replace-with-a-long-random-string \
    ghcr.io/mkappworks-dev/cloudzilla-app:v0.3.0

# run migrations once — the CLI is bundled in the same image
$ docker exec -it cloudzilla /app/cloudzilla-cli migrate
# then open http://localhost:8080 — first visit redirects to /setup

Docker Compose

If you want Postgres alongside it, this is the whole stack. After it's up, apply migrations once with docker compose exec cloudzilla /app/cloudzilla-cli migrate:

docker-compose.yml
services:
  cloudzilla:
    image: ghcr.io/mkappworks-dev/cloudzilla-app:v0.3.0
    ports: ["8080:8080", "2222:2222"]
    volumes: ["cloudzilla_data:/data"]
    environment:
      CZ_DATABASE_DSN: postgres://cz:secret@db:5432/cloudzilla?sslmode=disable
      CZ_SERVER_BASE_URL: https://git.example.com
      CZ_AUTH_JWT_SECRET: replace-with-a-long-random-string
      CZ_AUTH_COOKIE_SECURE: "true"
    depends_on: [db]
  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: cz
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: cloudzilla
    volumes: ["czdata:/var/lib/postgresql/data"]
volumes:
  czdata:
  cloudzilla_data:

From source

No release artifact yet — build it with make build, which embeds templates and compiled CSS into two static binaries (the server and the admin CLI). No Node.js or runtime to install.

build & run
$ git clone https://github.com/mkappworks-dev/cloudzilla-app.git
$ cd cloudzilla-app
$ make build   # produces dist/cloudzilla (server) and dist/cloudzilla-cli (admin)

$ export CZ_DATABASE_DSN=postgres://localhost/cloudzilla?sslmode=disable
$ export CZ_AUTH_JWT_SECRET=replace-with-a-long-random-string
$ ./dist/cloudzilla-cli migrate   # apply migrations once
$ ./dist/cloudzilla               # start the server on :8080

First run

Once you've run cloudzilla-cli migrate against an empty database, the server waits for you to create the first account.

  1. Run migrations. Apply the schema once with cloudzilla-cli migrate (see Installation). It's safe to re-run — applied migrations are skipped.
  2. Open the instance. Visit http://localhost:8080. The first request redirects to /setup.
  3. Create the superadmin. The first account created at /setup becomes the instance owner — full admin rights, including user and org management.
  4. Add an SSH key. Under Settings → SSH keys, paste your public key so you can push over SSH. See Git access.
  5. Create a repository and push your first commit. That's a working forge.

To invite more users when open registration is off, the superadmin manages invites and the allow_registration setting from Admin → Settings.

Configuration

Cloudzilla reads a config.yaml file, environment variables, or both — env vars (all prefixed CZ_) override the file, so containerized deployments can skip the file entirely. Dots in a config key map to underscores: auth.jwt_secretCZ_AUTH_JWT_SECRET. The essentials:

VariableDescriptionDefault
CZ_DATABASE_DSN PostgreSQL connection string. required
CZ_SERVER_BASE_URL Public URL of the instance. Used in clone URLs, webhooks, and OAuth callbacks. http://localhost:8080
CZ_SERVER_PORT Port the web server binds to. 8080
CZ_GIT_SSH_PORT Port the built-in Git SSH server binds to. 2222
CZ_GIT_REPOS_ROOT Filesystem path for bare Git repository storage. ./git-repos
CZ_AUTH_JWT_SECRET Signing secret for JWT session cookies. set in prod dev default
CZ_AUTH_COOKIE_SECURE Set the Secure flag on cookies — enable when serving over HTTPS. false

Always set CZ_AUTH_JWT_SECRET to a long, fixed random string in production. The built-in default is a known dev placeholder — leaving it in place lets anyone forge session tokens.

Email & OAuth (optional)

Both are off until configured. Set CZ_SMTP_HOST (plus CZ_SMTP_PORT, CZ_SMTP_USERNAME, CZ_SMTP_PASSWORD, CZ_SMTP_FROM, CZ_SMTP_TLS) to enable email notifications. For Google sign-in, set CZ_OAUTH_GOOGLE_CLIENT_ID, CZ_OAUTH_GOOGLE_CLIENT_SECRET, and CZ_OAUTH_GOOGLE_REDIRECT_URL (must match the URI registered in the Google console).

Git access

Cloudzilla speaks both smart HTTP and SSH out of the box, both implemented in pure Go. There is no system git binary involved on the server side.

Over SSH

Add your public key under Settings → SSH keys, then clone using the instance's SSH port (default 2222):

clone over ssh
$ git clone ssh://git@git.example.com:2222/acme/forge.git
# 2222 is the default SSH git port — see CZ_GIT_SSH_PORT

The server's SSH host key is generated on first boot (CZ_GIT_SSH_HOST_KEY, persisted in the data volume). Keep it stable across restarts — replacing it makes clients report REMOTE HOST IDENTIFICATION HAS CHANGED.

Over HTTPS

Use a personal access token as the password — generate one under Settings → Tokens. Tokens can be scoped read-only or read/write per repository.

clone over https
$ git clone https://git.example.com/acme/forge.git
# username: your handle · password: a personal access token

Reverse proxy & TLS

For production, terminate TLS at a reverse proxy and forward HTTP traffic to Cloudzilla. SSH stays a direct TCP passthrough on its own port. A minimal Caddy config:

Caddyfile
git.example.com {
    reverse_proxy localhost:8080
}

Set CZ_SERVER_BASE_URL to the public HTTPS URL so generated clone links, webhooks, and OAuth callbacks point at the proxy rather than the internal port. Behind TLS, also set CZ_AUTH_COOKIE_SECURE=true so session cookies carry the Secure flag.

Backups

Because all state — repositories, issues, users, settings — lives in PostgreSQL and on the repository volume, a backup is two things: a database dump and the data directory.

  • Database: a regular pg_dump of the Cloudzilla database captures all metadata and search indexes.
  • Repositories: snapshot the configured CZ_GIT_REPOS_ROOT (under Docker, the /data volume that also holds the SSH host key).
nightly dump
$ pg_dump cloudzilla | gzip > cloudzilla-$(date +%F).sql.gz

Upgrading

Upgrades are a binary (or image tag) swap, followed by a migration run. The server doesn't migrate on its own, so run the bundled CLI after pulling the new version.

  1. Back up the database first — see Backups.
  2. Pull the new tag (e.g. bump v0.3.0 → the next release).
  3. Run migrations with the new cloudzilla-cli migrate, then restart the container or service.
  4. Watch the logs — the instance reports it's serving once it's up.

Cloudzilla is in alpha — read the changelog before upgrading. Breaking changes are called out there per release.