Install and run¶
A controller, a database, and at least one agent. This page gets those three running and builds nothing yet; configuring a build plan is the next one.
What it needs¶
Java 24 or newer. For the controller and for an agent run as a process rather than a container.
PostgreSQL 17 or newer. The schema depends on partial unique indexes and
FOR UPDATE SKIP LOCKED, so an older major version will not start.
Docker. Only on agents that run Docker jobs. An agent running native jobs needs no daemon.
Somewhere to put bytes. Logs and artifacts go to a directory on the controller by default, or to any S3-compatible bucket.
Start the server¶
The build produces a container image of the controller and of the agent, and a boot jar of each. Run the controller against a PostgreSQL of its own:
docker run -d --name dutest-postgres \
-e POSTGRES_USER=dutest -e POSTGRES_PASSWORD=dutest -e POSTGRES_DB=dutest \
-p 5432:5432 postgres:17-alpine
java -jar controller.jar \
--spring.datasource.url=jdbc:postgresql://localhost:5432/dutest \
--dutest.base-url=https://dutest.example.com
Liquibase creates the schema on first start. Hibernate never generates any: the changelogs own it, so an upgrade is the new jar and nothing else.
The first start also creates an administrator and logs its password once. Take it out
of that log before anything rotates it away, or set dutest.auth.bootstrap-admin-password
yourself.
Open the server, sign in as admin, and create an API token under Profile, API
tokens. Everything below can be done from the pages instead; the token is what makes an
install repeatable.
export DUTEST_USER=admin
export DUTEST_TOKEN=scf_...
Set the base URL first¶
dutest.base-url is every address this server hands out: the link in a commit status,
the link in a notification, and the webhook URL a repository page shows somebody to paste
into their forge. Nothing in the process can work out its own external address — a
request's host header is whatever the last proxy put there, and a notification is sent
with no request in hand — so the default sends whoever clicks it to their own machine. The
server warns at startup if it is still the default and something would send links.
Connect an agent¶
An agent is its long-lived token, which it gets by presenting something on its first connect. There are two things it can present, and they answer different questions.
A cluster join token¶
One secret, set to the same value on the controller and on every agent, the way k3s joins a node. Nothing spends it and nothing expires it, so an agent that loses its credential presents it again and comes back as the agent it already was.
dutest:
agent:
# The same value in both. At least 32 random characters — the controller refuses to
# start on a shorter one, because a guessable join token is a route to running
# arbitrary code on every machine that builds here.
join-token: ${DUTEST_JOIN_TOKEN}
This is the one to reach for when the agents are deployed in the same pass as the server they dial. An enrolment token cannot be: it is issued from a controller somebody has already signed in to, which puts a person in the middle of the deployment.
An enrolment token, for a machine somebody is adding¶
Issued from the agents page or the API, spent on first connect, and expiring on its own. Where an agent has both, the enrolment token wins: it was set for that machine.
curl -sX POST https://dutest.example.com/api/v1/agents/enrolment-tokens \
-u "$DUTEST_USER:$DUTEST_TOKEN" -H 'Content-Type: application/json' \
-d '{"uses":6}'
uses is how many agents may enrol with it, and it exists for replicas deployed from one
Secret: without it the first replica spends the token, the rest are refused, and they go
on running as the agent the first one became. What widens is how many machines a token
joins, never how long it is a way in.
Then start the agent with it. It is needed for the first connect and never again:
java -jar agent.jar \
--dutest.agent.controller-host=dutest.example.com \
--dutest.agent.controller-port=9090 \
--dutest.agent.enrolment-token=scfenr_...
What an agent says it can do¶
An agent advertises capabilities, and a job asks for them with requires. os and
arch are detected and advertised without being configured; anything else is yours to
name.
dutest:
agent:
executors: RUNTIME_TYPE_NATIVE,RUNTIME_TYPE_DOCKER
max-concurrent-jobs: 2
capabilities:
dockerd: "true"
gpu: "true"
An agent that should never run a pipeline's own commands as its own user is given
RUNTIME_TYPE_DOCKER alone. The native runtime runs steps as the agent's user with
nothing between them and the machine, which is the right default for build machines you
own and the wrong one for code you do not trust.
Agent settings¶
| Setting | Default | Notes |
|---|---|---|
dutest.agent.controller-host |
localhost |
|
dutest.agent.controller-port |
9090 |
Where the controller listens for gRPC |
dutest.agent.join-token |
unset | The cluster's join token, the same value the controller has |
dutest.agent.enrolment-token |
unset | Needed only for the first connect; wins over the join token |
dutest.agent.executors |
RUNTIME_TYPE_NATIVE |
Add RUNTIME_TYPE_DOCKER |
dutest.agent.capabilities |
{} |
Matched against a job's requires |
dutest.agent.max-concurrent-jobs |
1 |
|
dutest.agent.one-shot |
false |
Run one job, then stop the process |
dutest.agent.idle-timeout-seconds |
0 |
Stop after this long with no work; 0 waits |
dutest.agent.sandbox |
off | no-new-privileges, a read-only root and a CPU cap for Docker jobs |
The stricter sandbox settings are off because they break builds that exist:
no-new-privileges stops sudo, and a read-only root stops most build scripts. Turn them
on where you build code you do not trust. A Docker job already runs with the capabilities
every container escape needs dropped, and with a cap on its process count, so a fork bomb
takes its own job down rather than the agent.
Licensing¶
A server with no licence runs 10 concurrent jobs, for as long as you like, with no account and no payment details. Beyond that, a licence key is pasted under Site administration, Licence. It is a signed file checked against a key the server already has, so nothing about your builds depends on anything outside your network being reachable.
Next¶
Configuring a build plan takes it from here. Deploying it covers the settings worth getting right before other people depend on this server.