Saphan StudioDocs
Deployment

The built-in git server, and choosing another forge

Every Studio install brings its own git server, Gitea. Start it, serve it over TLS, or point Studio at your own Gitea or at GitHub instead.

Every Studio install brings up its own git server — Gitea, the default forge. You do not have to use it: you can point Studio at your own Gitea server, or at GitHub.

⚠ What is and is not proven. The start logic, the installer and the service plan are tested. Starting a real Gitea container, and running a real Gitea binary as a service, are not yet measured. Follow these steps on a machine you can afford to learn on first.

What you get

Container install (every edition)Native install (Team and Enterprise)
How it runsa container, started with saphan infra up giteaa service beside the other Saphan services, registered by saphan service install
Where its data istwo named volumes: saphan-gitea-data (repositories) and saphan-gitea-config<installation root>/gitea/, all mode 0700
After a restartcomes back by itselfthe same autostart rule as the other services
Listens onforge.bundled_listen, default 127.0.0.1:3000 — loopback onlythe same key and default
Pinned bythe image's SHA-256 digestthe release's checksum list, checked by the installer

Gitea speaks plain HTTP, so a non-loopback forge.bundled_listen is refused (forge-bundled-plain-http). To serve other machines, keep Gitea on loopback and put a TLS reverse proxy in front of it (below).

Start it — container install

  1. Put the deployment files on the machine (existing files are kept):

    saphan infra write
  2. Start it (--runtime podman for Podman):

    saphan infra up gitea

    If it answers this build carries no resolved digest, resolve one yourself and pass it in. An image that is not pinned by @sha256: is refused, because this server holds source code:

    docker buildx imagetools inspect docker.io/gitea/gitea:28.2.0-rootless
    saphan infra up gitea --image docker.io/gitea/gitea:28.2.0-rootless@sha256:<digest>
  3. Read the administrator's password. The login is saphan-admin; the password is generated on the first start, kept on later ones, and never put in an environment variable, a command line or a log:

    cat /opt/saphan/docker/secrets/gitea-admin-password
  4. Check it answers — a JSON body with a version. The administrator account is created shortly after the server is healthy; give it a minute on the first start.

    curl -fsS http://127.0.0.1:3000/api/v1/version
  5. To stop it without losing anything: docker compose -f /opt/saphan/docker/saphan-gitea-compose.yml down keeps both volumes. ⛔ down -v deletes the repositories.

Lost the administrator password? Keep the volumes, and run the same saphan infra up gitea command again. If the password file is gone, the command sets a new password for the existing administrator and writes it to the same file, without printing it.

Start it — native install

  1. The installer places a gitea binary beside the other programs when the release carries one, and says so when it does not.

  2. Register the services:

    saphan service install

    A machine-wide installation runs its services as root, and Gitea refuses to run as root, so name an unprivileged user with --gateway-user <user>. Without one, the install prints NO GIT SERVER UNIT WAS REGISTERED and why.

  3. Read the administrator password from the same file as above; the login is saphan-admin.

  4. Upgrade. Installing a newer Studio installs the newer Gitea with it. The installer refuses to put an older Gitea over a newer one, because Gitea migrates its database forward and cannot migrate it back; --allow-downgrade overrides it, at your own risk.

  5. Uninstall. saphan service uninstall removes the services and keeps <root>/gitea/ — those are your repositories. It prints the directory.

Serve it over TLS

  1. Keep Gitea on loopback (saphan config set forge.bundled_listen 127.0.0.1:3000).

  2. Point a DNS name at the machine, open TCP 80 and 443 for your proxy, and keep TCP 3000 closed to other machines. One Caddy configuration:

    Caddyfile
    git.example.com {
        reverse_proxy 127.0.0.1:3000
    }

    Any other TLS proxy works if it passes requests to http://127.0.0.1:3000 and forwards the original Host and scheme.

  3. Tell Gitea its public address: for the container, saphan infra up gitea --root-url https://git.example.com/; for the native install, set ROOT_URL in <root>/gitea/custom/conf/app.ini and restart the service. Check with curl -fsS https://git.example.com/api/v1/version.

Choose another forge

forge.kind takes bundled-gitea (the default for a new install), gitea or github.

Your own Gitea server (or one compatible with Gitea's API):

saphan config set workspace.canon origin
saphan config set forge.kind gitea
saphan config set forge.base_url https://git.example.com:443
saphan config set forge.repo owner/name
saphan config set forge.token_lane forge-bot
saphan config set forge.enforcement status-check

base_url carries the scheme and an explicit port. The token is never in the configuration: token_lane names a lane whose credential is read when it is used. While forge.kind is not bundled-gitea, saphan service install registers no git server.

GitHub: saphan config set forge.kind github, and the rest as above with base_url https://api.github.com:443. ⚠ Saphan Studio reads GitHub and does not write to it — see pull requests and forges.

Back to the bundled server: saphan config set forge.kind bundled-gitea. It needs no base_url; until you bind a repository to it, it needs no repo, token_lane or enforcement.

When it refuses

you seemeaningdo
… carries no resolved digestthis build ships no image pinpass --image …@sha256:<digest>
--image … is not pinned by digestthe reference is a taggive a digest-pinned reference
… is not there — … saphan infra writethe compose file is not on this machinerun saphan infra write
NO GIT SERVER UNIT WAS REGISTERED — no gitea binary …the installed release carries no Giteause a release that carries one, or the container
… a machine-wide installation runs its units as root …Gitea refuses root--gateway-user <user>
this would replace gitea X with the older Ya downgradedo not, or pass --allow-downgrade
forge-bundled-listenforge.bundled_listen is not host:portuse host:port, port 1–65535
forge-bundled-plain-httpforge.bundled_listen is not a loopback addresskeep it on loopback and use a TLS proxy

On this page