Deploy to the Ubuntu VM (all-in-one) — LAN / HTTP
Everything runs on one Ubuntu VM, one Docker daemon: Gitea, the container
registry, the build, and the ROLAC runtime. So a single Gitea Actions job (the
ubuntu runner) does the whole pipeline — no cross-machine pull, no Windows-runner
quirks.
git push main
│
▼
Gitea (on the VM) ── triggers .gitea/workflows/ci-cd-vm.yml
│
▼
ubuntu runner (on the VM, same Docker daemon)
dotnet test
docker build ./API + ./APP -> :latest + :<sha>
docker push -> git.golife.love/chrischen/rolac-{api,app}
docker compose up -d (TAG=<sha>)
curl /api/health
│
▼
browser -> http://<vm-ip>:8080
│ nginx edge (container, 8080->80)
├── / -> app container (Angular static)
└── /api/ -> api container (ASP.NET, :8080)
api ──> existing PostgreSQL @ 192.168.68.55:49154 (not containerized)
No TLS yet — plain HTTP on port 8080. Add Let's Encrypt later (see the Azure
deploy/ files) or front it with an existing reverse proxy.
One-time setup — on the VM
-
Deploy dir + secrets:
sudo mkdir -p /home/chris/docker/rolac/nginx/conf.d /home/chris/docker/rolac/data/api-storage sudo cp /path/to/repo/deploy/vm/.env.example /home/chris/docker/rolac/.env sudo nano /home/chris/docker/rolac/.env # real DB user/password + JWT_SECRET + APP_ORIGINMake sure the user the runner executes as can read/write
/home/chris/docker/rolac. -
Registry token — in Gitea: Settings → Applications → new token with
read:package+write:package. Log Docker in once on the VM:docker login git.golife.love -u ChrisChen # paste the token -
Install act_runner on the VM with the label
ubuntuand access to the host Docker. The runner must be able to rundotnet,docker, anddocker compose, and reach/home/chris/docker/rolac:docker run -d --restart unless-stopped --name rolac-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /home/chris/docker/rolac:/home/chris/docker/rolac \ -e GITEA_INSTANCE_URL=https://git.golife.love \ -e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \ -e GITEA_RUNNER_LABELS=ubuntu \ gitea/act_runner:latestThe job calls
dotnet testanddocker builddirectly. If act_runner runs in a container, that image needs the .NET 8 SDK + docker CLI on PATH. Simplest: install act_runner as a native binary on the VM (it then uses the host's Docker + an installed .NET SDK). Either way the label must beubuntu. -
Gitea repo secrets (Settings → Actions → Secrets):
REGISTRY_USER=ChrisChenREGISTRY_TOKEN= the package token from step 2
-
Enable Actions for the repo if needed (Settings → Advanced → Actions).
Day-to-day
git push to main → .gitea/workflows/ci-cd-vm.yml:
test → build both images → push to registry → sync compose/nginx → compose up -d → health check.
Open http://<vm-ip>:8080 and log in.
Deploy pins TAG=<git-sha>, so the running containers match exactly the commit that
was built (the images already exist in the local Docker, so this is instant).
Manual deploy (no runner yet)
On the VM, from a checkout of the repo:
docker login git.golife.love -u ChrisChen
docker build -t git.golife.love/chrischen/rolac-api:latest ./API
docker build -t git.golife.love/chrischen/rolac-app:latest ./APP
mkdir -p /home/chris/docker/rolac/nginx/conf.d /home/chris/docker/rolac/data/api-storage
cp deploy/vm/docker-compose.yml /home/chris/docker/rolac/docker-compose.yml
cp deploy/vm/nginx/conf.d/rolac.conf /home/chris/docker/rolac/nginx/conf.d/rolac.conf
cd /home/chris/docker/rolac && docker compose up -d
curl -fsS http://localhost:8080/api/health
Notes
- First boot runs DB migrations against
192.168.68.55automatically (Program.cscallsMigrateAsync()+ seed). The VM must reach that host and the DB user needs DDL rights; back up before the first run. - Uploaded files persist under
/home/chris/docker/rolac/data/api-storage. - Same Docker daemon for build + run means
docker compose upfinds the freshly built:<sha>images locally;docker compose pullis unnecessary here (but harmless if you add it). - To go HTTPS later: switch the edge to ports 80/443 and mount Let's Encrypt certs,
or use the Azure
deploy/files which already include certbot.