Setting Up OneDev on Windows Without Docker (Because Docker Wouldn't Start)
This is specifically a Windows story. Wanted to try OneDev — a self-hosted Git server with built-in CI/CD and Kanban boards, lighter than GitLab, more all-in-one than Gitea — on a Windows box. The official install path is docker run ..., which assumes Docker just works. Mine didn't: Docker Desktop wouldn't start at all. If you're on Linux with Docker already working, the official route is simpler and most of this won't apply to you. If you're stuck on Windows without a working Docker Desktop, here's what actually got it running instead — bare-metal, as a plain Java app.
Why Docker didn't work
Docker Desktop's engine returned a 500 — its docker-desktop WSL distro never registered. Installing a plain Ubuntu WSL distro as a workaround also failed:
WSL2 is not supported ... enable the "Virtual Machine Platform" optional component
Error ...CreateVm/HCS/HCS_E_HYPERV_NOT_INSTALLED
Root cause: the Virtual Machine Platform Windows optional feature was off. Both Docker Desktop and WSL2 need it to create their lightweight VM. Hardware virtualization was enabled fine in BIOS (systeminfo reports "A hypervisor has been detected") — this was a pure software/feature-flag issue, not a hardware one. Fixable with a reboot, but I didn't want to reboot the box right then, so:
OneDev is a plain Java application. It sidesteps all of this entirely.
Install (bare-metal, no Docker, no reboot)
# 1. Download the server bundle (~168 MB)
New-Item -ItemType Directory -Force -Path "C:\onedev" | Out-Null
curl.exe -L --fail -o "C:\onedev\onedev-latest.zip" `
"https://code.onedev.io/onedev/server/~site/onedev-latest.zip"
# 2. Extract
Expand-Archive -Path "C:\onedev\onedev-latest.zip" -DestinationPath "C:\onedev" -Force
# 3. Start in console mode (first boot builds the DB schema, ~30s)
& "C:\onedev\onedev-latest\bin\server.bat" console
When the log prints Please set up the server at http://...:6610, open it in a browser. Prerequisites are just JDK 11+ and Git 2.11.1+ — both were already on this box.
First-run wizard
- Create an admin account.
- System Setting step — set Server URL to
http://localhost:6610for local-only use (it auto-fills the machine hostname, which only matters if you'll reach OneDev from other devices on the LAN — this URL gets baked into clone URLs, emails, and webhooks). Point Git and curl at "Use in System Path." - Skip SMTP if it's just for local use.
Database is bundled HSQLDB (file-based, zero config) — fine to start, movable to MySQL/Postgres later before anything important lives in it.
Make it a permanent Windows service
Console mode dies when the terminal closes. To auto-start with Windows:
# Elevated prompt — needs admin/UAC, but no reboot
C:\onedev\onedev-latest\bin\server.bat install
Creates a Windows service named OneDev, managed with Start-Service / Stop-Service afterward.
Getting CI working — no Docker means no default executor
OneDev's default job executor is Docker-based, which obviously doesn't work here. Add a Server Shell Executor instead (Administration → Job Executors → Add), so jobs run directly on the host:
- Job Match must be non-empty —
all/trueare rejected with "Malformed job match." Use a real query, e.g."Project" is "myproject". - Commands then run in Windows cmd/batch directly, using whatever's on the host (git, PHP, composer, node...). Any container
imagefield in your buildspec is simply ignored.
A working buildspec (schema version 52, OneDev 16.0.1):
version: 52
jobs:
- name: hello-world
jobExecutor: shell
steps:
- !CheckoutStep
name: checkout
cloneCredential: !DefaultCredential {}
condition: SUCCESSFUL
- !CommandStep
name: greet
runInContainer: false
interpreter: !WindowsBatchInterpreter
commands: |-
echo Hello from OneDev CI running on %COMPUTERNAME%
git --version
condition: SUCCESSFUL
triggers:
- !BranchUpdateTrigger
userMatch: anyone
retryCondition: never
Prefer the visual editor ("Add .onedev-buildspec.yml" on the project) — it writes valid YAML for you. Hand-editing means matching the schema exactly, and there are real footguns:
- `version: 52` is current for 16.0.1 — the number is the highest
migrateNmethod in the server jar, not an incrementing "current version + 1."version: 53fails with "Cannot find migrate method migrate53." - Step
conditionenum isSUCCESSFUL|ALWAYS|NEVER— not the longer name you'd guess. - Interpreter tags are
!WindowsBatchInterpreter|!PosixInterpreter|!PowerShellInterpreter. No!DefaultInterpreterfor a container-less run. - Interpreter
commandsis a single multi-line string (|-block), not a YAML list. !BranchUpdateTriggerrequiresuserMatch— useanyone, or a real UserCriteria/GroupCriteria. Leaving it empty or writing something like"Name" is "x"both fail validation.- Parse/validation errors are silent in the UI. If a build just never appears after a push, check the server log for
WARN ... Malformed build specbefore assuming something else is wrong.
If you've set up Laravel with Bitbucket Pipelines before, the shape of a build pipeline here will look familiar — same idea (checkout, install, test), different YAML dialect.
Test and coverage reports
OneDev ingests standard report formats (JUnit, Clover, Cobertura, JaCoCo, Jest, Checkstyle, ESLint, PMD, and more) and renders them as build tabs, with coverage diffs on PRs. For PHP:
- !CommandStep
name: test
runInContainer: false
interpreter: !WindowsBatchInterpreter
commands: |-
composer install --no-interaction --prefer-dist
vendor\bin\phpunit --log-junit build\junit.xml --coverage-clover build\clover.xml
condition: SUCCESSFUL
- !PublishJUnitReportStep
name: publish tests
reportName: PHPUnit
filePatterns: build/junit.xml
condition: ALWAYS
- !PublishCloverReportStep
name: publish coverage
reportName: Coverage
filePatterns: build/clover.xml
condition: ALWAYS
You need a coverage driver (Xdebug or PCOV) installed on the PHP the executor uses — without one, PHPUnit silently produces no coverage data at all. And the publish steps must be condition: ALWAYS, not SUCCESSFUL — otherwise a failing test aborts the job before the report ever publishes, and you lose exactly the information you wanted from the failure.
I've got more detail on getting Xdebug/PCOV actually working (including the Mac M1 install quirks) in Adding Code Coverage.
SonarQube — pipeline-level only
There's no native OneDev plugin for SonarQube. It's a plain command step:
- !CommandStep
name: sonar
runInContainer: false
interpreter: !WindowsBatchInterpreter
commands: |-
sonar-scanner -Dsonar.host.url=@secret:sonar_url@ -Dsonar.token=@secret:sonar_token@ -Dsonar.qualitygate.wait=true
What you get in OneDev is just the scanner's log output plus a red/green build (red if the quality gate fails). No coverage tab, no findings list, no PR decoration inside OneDev itself — all of that lives in the SonarQube dashboard, since SonarQube doesn't support OneDev as a DevOps platform integration. Store the token as a OneDev job secret rather than hardcoding it. If you don't need a full SonarQube server, OneDev's own Checkstyle/PMD/SpotBugs report steps cover a good chunk of the "quality findings on PRs" use case without standing up Sonar at all.
If you want to go further with SonarQube specifically — including getting free GitHub PR decoration out of the Community edition — I wrote up the gotchas from that separately.
REST API quick reference
Base: http://localhost:6610/~api/. Auth via Authorization: Bearer <token> (create one under your avatar → Access Tokens; it also works as the git-over-HTTP password).
GET /~api/users/me
GET /~api/projects?query=&offset=0&count=N
GET /~api/builds?query="Project" is "myproject"&offset=0&count=N
GET /~api/builds/{id}
GET /~api/settings/job-executors # POST the full JSON array back to change executors