Bahriya Tersane

Documentation / onedev

OneDev

Installs OneDev: Git repositories with code review and pull requests, issues, and container, npm and Maven registries, in one server.

The chart provides:

  • OneDev 17.1, for amd64 and arm64, with its database in a PostgreSQL of your own, such as a release of the postgresql chart, reached over TLS with the server’s certificate checked;
  • repositories, packages and build artifacts on a volume that is kept when the release is uninstalled;
  • the first administrator’s password and the database credentials taken from Secrets you create, which the chart never creates, changes or prints;
  • Git over HTTPS through an Ingress, and Git over SSH on a Service of its own;
  • no permission in the cluster, unless you enable the Kubernetes executor;
  • a NetworkPolicy;
  • OneDev’s image from the Mamluk registry, pinned by digest, and a Pod meeting the restricted Pod Security Standard with a read-only root filesystem.

OneDev’s own build pipelines are not the subject of this chart. For continuous integration, use the concourse chart. See “Builds in the cluster” below for what the Kubernetes executor would require.

Prerequisites

  • Kubernetes 1.33 or later.
  • A pull secret for 1x.ax named 1x-ax in the release’s namespace, created from an image token as described in the access documentation.
  • A PostgreSQL database owned by the user OneDev connects as. A release of the postgresql chart in the same namespace is the simplest:

    helm install pg tersane/postgresql --namespace git --create-namespace \
      --set bootstrap.database=onedev --set bootstrap.owner=onedev
    

    Its pg-postgresql-app Secret holds the user and password, and its pg-postgresql-ca Secret the certificate authority OneDev checks the server against.

  • A Secret holding the first administrator’s password:

    kubectl --namespace git create secret generic onedev-admin \
      --from-file=password=./password
    

Installation

helm install onedev tersane/onedev --namespace git \
  --set externalURL=https://git.example.com \
  --set admin.email=<administrator's email address> \
  --set admin.existingSecret=onedev-admin \
  --set database.host=pg-postgresql-rw \
  --set database.existingSecret=pg-postgresql-app \
  --set database.caSecret=pg-postgresql-ca \
  --set ingress.className=<class> --set ingress.tlsSecret=<certificate Secret> \
  --version <chart version>

The release’s resources are named <release>-onedev (here onedev-onedev), or after the release alone if its name already contains onedev. OneDev creates its tables when it first starts. Check that the Pod is ready:

kubectl --namespace git get pods -l app.kubernetes.io/instance=onedev

Then sign in at the external URL as root, or the name in admin.username.

The administrator, externalURL and ssh.url are read once, when OneDev first creates its database. Changing these values afterwards changes nothing: change them in OneDev itself, under Administration.

OneDev runs as one server. Running several servers as a cluster is a feature of OneDev’s enterprise edition, which this chart does not configure.

Database

OneDev connects with the JDBC driver it includes. database.sslmode is verify-full by default: the connection is encrypted and the server’s certificate is checked against the authority in database.caSecret, and must name database.host. require encrypts without checking the certificate, and disable does not encrypt.

Exposing OneDev

The chart’s Ingress serves the host in externalURL and sends everything to OneDev. Whatever proxy is in front of OneDev must do three things, each learnt on a OneDev in production:

  1. Not limit the request body. Pushes and package uploads can be any size.
  2. Buffer responses, with no small cap on the buffer. OneDev’s web server closes a connection that has been idle for 30 seconds. A client that pauses while downloading a large layer, unbuffered, has its download cut. A proxy that buffers reads from OneDev at its own speed. nginx’s default buffer cap of 1 GiB cuts every larger download: raise proxy_max_temp_file_size above your largest layer.
  3. Answer range requests itself. OneDev ignores Range and returns the whole file, so an interrupted pull starts again from the beginning. nginx does this with proxy_force_ranges on.

Do not lengthen idle timeouts on the Git and SSH paths to hide slow clients: CI systems poll repositories every few seconds, and long-lived idle connections then accumulate.

With service.type: NodePort and the Ingress disabled, OneDev is reached on the nodes over plain HTTP, which only suits a test.

Git over SSH

ssh:
  enabled: true
  url: ssh://git.example.com:22
  service:
    type: LoadBalancer

SSH has a Service of its own, <release>-onedev-ssh, so it can be exposed apart from the web interface: as a LoadBalancer, a NodePort, or through an Ingress controller that carries TCP. ssh.url is the address OneDev shows in clone commands. Limit who reaches it with networkPolicy.sshFrom.

Trusted authorities

OneDev trusts the system’s authorities and those in trustedCA: an OIDC provider’s, a mail server’s, or a remote repository’s that OneDev mirrors. Each entry names a Secret in the release’s namespace and the key holding the certificate. The database’s authority is added with them.

Builds in the cluster

This section matters only if you set kubernetesExecutor.enabled. It is off by default, and Concourse, through the concourse chart, is the CI we recommend.

OneDev’s Kubernetes executor runs each build as Pods in the cluster. To do so, OneDev 17.1 creates a new namespace for every build, labelled with the Pod Security level privileged, and deletes it afterwards. There is no setting to make it use a namespace of your choosing.

Enabling it gives OneDev a ClusterRole with exactly what the executor does:

Resource Verbs
namespaces create, delete, list
pods create, delete, get, watch
pods/log get
pods/exec create
services, secrets, configmaps, persistentvolumeclaims create, delete
clusterrolebindings create, delete

and, by a Role in kube-system only, list on Services there, which the check OneDev runs before every build (kubectl cluster-info) needs.

The chart grants no read access to Secrets, nodes or deployments, which OneDev’s own chart grants and its executor does not use.

Every build Pod also runs OneDev’s helper image, 1dev/k8s-helper, at the version the installed OneDev names in its code, from Docker Hub. OneDev offers no setting to change it, so a cluster without access to Docker Hub needs a containerd mirror for it, as for the images the build steps name.

This is cluster-admin in effect. Creating Pods in any namespace means running as any ServiceAccount in that namespace and mounting any of its Secrets, including in system namespaces. Anyone who can change a build specification or the executor’s settings in OneDev holds that power. Enable it only on a cluster dedicated to builds, if at all.

The Compliance and Assurance Convoy refuses these builds. With its policies at Deny, every build namespace is privileged by OneDev’s own design and is created afresh, so it can be neither brought within the policies nor exempted by name. Builds through the executor fail on such a cluster.

Upgrades

Each chart version pins OneDev’s image by digest. When the image changes, an init container upgrades the installation on the volume before OneDev starts, and OneDev migrates its database when it starts. Where the new version changes the database’s structure, the upgrade also exports the database to the volume and imports it again with the new program, so it takes longer the larger the database. Neither is reversed by rolling back the chart, so back up the database and the volume first, and read OneDev’s release notes before moving to a new major version. The server is stopped before the new one starts: there is one server, and an upgrade has a short outage.

Backups and restore

A complete backup is two things, taken together:

  1. The database: the postgresql chart’s backups to S3-compatible storage, with continuous WAL archiving.
  2. The volume <release>-onedev-data, which holds the repositories, the packages and the build artifacts: a snapshot through your storage’s CSI driver, or a copy of the volume. A volume copy older than the database backup lacks content the database refers to, so take it after the database backup.

To restore, recover the database into a new postgresql release, make the volume copy available as a claim, and install OneDev with persistence.existingClaim naming it.

Network policy

The NetworkPolicy admits HTTP to OneDev from networkPolicy.ingressFrom, set it to your Ingress controller, and SSH from networkPolicy.sshFrom. Either empty admits any source, as a NodePort or LoadBalancer requires.

Pod Security

The Pod meets the restricted Pod Security Standard: OneDev runs as user 10000 with a read-only root filesystem, no privilege escalation, every capability dropped and the runtime’s default seccomp profile. It holds no service account token unless the Kubernetes executor is enabled.

OneDev’s image starts as root from a script in /root, and copies its installation onto the volume with a program that writes beside itself. The chart instead starts OneDev directly: an init container copies the installation to scratch space and runs OneDev’s upgrade from there, which fills the volume on the first start and upgrades it when the image changes.

The Pod shares one process namespace, so the Pod’s pause process is its first process and would reap any process left without a parent. This is a precaution: our tests have not produced such processes, with or without it, so they do not show that it is needed.

Uninstalling

helm uninstall onedev --namespace git

The volume is kept. The database is yours, and is untouched.

Parameters

OneDev

Parameter Description Default
externalURL The address users reach OneDev at, with its scheme; required; read once ""
image.repository / image.tag The image 1x.ax/tersane/core/onedev/server / the release’s
imagePullPolicy Pull policy IfNotPresent
imagePullSecrets Pull secrets [{name: 1x-ax}]
admin.username / admin.email The first administrator; the email is required; read once root / ""
admin.existingSecret / admin.passwordKey The Secret with the first administrator’s password; required "" / password
server.maxMemoryPercent Percentage of the memory limit for the Java heap 50
server.resources Resources of the OneDev container 500m and 2Gi requested, 4 CPUs and 4Gi limits

Database and storage

Parameter Description Default
database.host / database.port / database.name The PostgreSQL server and database; host required "" / 5432 / onedev
database.existingSecret / database.usernameKey / database.passwordKey The Secret with the user and password; required "" / username / password
database.sslmode disable, require, verify-ca or verify-full verify-full
database.caSecret / database.caKey The server’s certificate authority "" / ca.crt
database.maxPoolSize Connections OneDev holds 25
persistence.size / persistence.storageClass The volume 100Gi / the default class
persistence.existingClaim A claim of your own instead ""
trustedCA Further authorities, as {secret, key} entries []

Access

Parameter Description Default
service.type / service.port / service.nodePort The web Service: ClusterIP or NodePort ClusterIP / 80 / ""
ssh.enabled Git over SSH on a Service of its own false
ssh.url The SSH address shown in clone commands; required with SSH; read once ""
ssh.service.type / .port / .nodePort The SSH Service ClusterIP / 22 / ""
ingress.enabled / ingress.className The Ingress true / ""
ingress.annotations / ingress.tlsSecret Its annotations and certificate {} / ""
ingress.serviceAnnotations Annotations on the web Service {}
networkPolicy.enabled The NetworkPolicy true
networkPolicy.ingressFrom / networkPolicy.sshFrom Sources admitted to HTTP and SSH; empty admits any [] / []
kubernetesExecutor.enabled Builds as Pods in the cluster; cluster-admin in effect; see above false

Pod

Parameter Description Default
podSecurityContext The Pod’s security context user 10000, restricted
securityContext Each container’s security context read-only root, nothing added
nodeSelector / tolerations Scheduling {} / []