1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
# Bastion — Claude Code Context
## Project Overview
Bastion is a self-hosted server system on Debian that runs multiple services in Docker containers. All containers are managed via Portainer. Nginx acts as reverse proxy sitting on all service networks.
## Domain
- Primary domain: `swave.lol`
- Subdomains: `blog.swave.lol`, `ghost.swave.lol`, `jenkins.swave.lol`, `cgit.swave.lol`, `gerrit.swave.lol`, `nexus.swave.lol`, `registry.swave.lol`, `auth.swave.lol`
## Services / Stacks
### Portainer
- Standalone `docker run`, not in a compose file
- Web UI on port 9000
- Used to manage all other containers/stacks
### Ghost (blog)
- Dir: `ghost/`
- Compose: `ghost/compose.yml`, env: `ghost/stack.env` (for Portainer) or `.env` (for local)
- Network: `ghost_network`
- Services: ghost (port 2368), ghost-db (MySQL 8), activitypub (optional, profile-gated)
- Accessible at `https://swave.lol`, `https://blog.swave.lol`, and `https://ghost.swave.lol`
- URL must be configured as `https://` in compose.yml (Ghost caches URL config internally)
- DOMAIN in stack.env must be the bare domain (e.g., `swave.lol`) without a port number
### Git Server
- Dir: `git-server/`
- Compose: `git-server/server.yaml`, needs `docker build -t git-server .` first
- Networks: `git-network` (172.22.0.0/16, static IP 172.22.0.2) and `jenkins_network` (172.23.0.0/16, static IP 172.23.0.3)
- Ports: 22 (SSH), 9418 (git daemon)
- Volumes: `/var/git_ssh_keys` (public keys), `/var/git/repos` (repositories)
- Host SSH runs on a non-standard port, so port 22 is free for git
### Cgit (git web interface)
- Dir: `git-server/cgit/`
- Builds from `git-server/cgit/Dockerfile` (Alpine + cgit + fcgiwrap + nginx)
- Part of `git-server/server.yaml` compose stack
- Network: `git-network`
- Mounts `/var/git/repos:/repos:ro` (read-only, shared with git-server)
- Auto-discovers all bare repos via `scan-path=/repos`
- No host ports (accessed only via main nginx reverse proxy)
- Accessible at `https://cgit.swave.lol` and `https://swave.lol/cgit`
### Gerrit (code review)
- Part of `git-server/server.yaml` compose stack
- Image: `gerritcodereview/gerrit:latest` (no custom Dockerfile)
- Networks: `git-network` (static IP 172.22.0.4) and `jenkins_network` (static IP 172.23.0.4)
- Web UI on port 8080 internally, SSH on 29418
- No host ports (accessed only via main nginx reverse proxy)
- Volumes: `/var/gerrit` (etc, git, db, index, cache), shared `/var/git/repos`
- Accessible at `https://gerrit.swave.lol` (swave.lol/gerrit redirects to subdomain)
- Auth: HTTP header auth (`X-Forwarded-User` from Authelia), anonymous read allowed
- nginx auth_request only on `/login/` path — Gerrit manages its own session cookie after login
- `loginUrl = https://gerrit.swave.lol/login/` in gerrit.config triggers Authelia SSO via nginx
- Config template: `git-server/gerrit.config.example` (real config gitignored, lives at `/var/gerrit/etc/gerrit.config`)
### Jenkins
- Dir: `jenkins/`
- Compose: `jenkins/docker-compose.yaml`, builds from `jenkins/Dockerfile`
- Network: `jenkins_network` (172.23.0.0/16, static IP 172.23.0.2)
- Uses Docker-out-of-Docker (DooD): mounts `/var/run/docker.sock` to spawn sibling build containers
- Dockerfile extends `jenkins/jenkins:lts` with Docker CLI and `docker-workflow` plugin
- No path prefix (JENKINS_OPTS is empty); accessible at `https://jenkins.swave.lol/`
- `swave.lol/jenkins` redirects to `jenkins.swave.lol/` (strips the prefix)
- Ports: 8080 (web UI), 50000 (agent communication via JNLP, used by remote build agents)
- Volume: `/var/jenkins_home`
- Auth: Authelia OIDC (`oic-auth` plugin) — `authorization_policy: one_factor` required in Authelia client config
- OIDC redirect URI: `https://jenkins.swave.lol/securityRealm/finishLogin` (must bypass nginx auth_request)
- Username field: `preferred_username` (maps Authelia username to Jenkins user)
- Jenkins URL setting must be `https://jenkins.swave.lol/`
- Pipeline durability: set to "Performance-optimized" to prevent flow execution corruption
- Startup script: `jenkins/init.groovy.d/clear-stuck-builds.groovy` auto-cleans stuck builds on restart
- Currently runs all builds on the controller via DooD; supports adding permanent SSH agents for scaling (see setup guide 6.6)
### Nexus (artifact repository & Docker registry)
- Dir: `nexus/`
- Compose: `nexus/docker-compose.yaml`
- Image: `sonatype/nexus3:latest` (no custom Dockerfile)
- Network: `nexus_network` (172.25.0.0/16, static IP 172.25.0.2)
- Nexus web UI on port 8081 internally, Docker registry API on port 5000
- No host ports (accessed only via main nginx reverse proxy)
- Volume: `/var/nexus-data` (must be owned by UID 200)
- Accessible at `https://nexus.swave.lol` and `https://swave.lol/nexus`
- Docker registry accessible at `https://registry.swave.lol`
- Docker registry requires manual setup after first login: create a "docker (hosted)" repository with HTTP connector on port 5000
### Netdata (monitoring)
- Dir: `netdata/`
- Compose: `netdata/docker-compose.yaml`
- Image: `netdata/netdata:latest`
- Network: `monitoring_network` (172.24.0.0/16, static IP 172.24.0.2), port 19999 internally
- No host ports (accessed only via main nginx reverse proxy)
- Accessible at `https://swave.lol/netdata/`
- Auth: Authelia forward-auth (one_factor) — protected via nginx auth_request on the /netdata/ location
- nginx proxies with `Cache-Control: no-store` to prevent browser caching (avoids CORS errors on cached API calls)
- nginx-netdata.conf snippet: `netdata/nginx-netdata.conf`
### Authelia (SSO / authentication)
- Dir: `authelia/`
- Compose: `authelia/docker-compose.yaml`
- Image: `authelia/authelia:latest`
- Network: `authelia_network` (172.26.0.0/16, static IP 172.26.0.2)
- Services: authelia (port 9091), authelia-redis (session store)
- Volumes: `/var/authelia/config`, `/var/authelia/data`, `/var/authelia/secrets` (ro), `/var/authelia/redis`
- Secrets loaded from files in `/var/authelia/secrets/` (JWT, session, storage encryption, OIDC HMAC, OIDC RSA key)
- Accessible at `https://auth.swave.lol`
- Forward-auth (auth_request) for: Netdata, Cockpit, Gerrit (/login path), Jenkins
- OIDC provider for Jenkins (full SSO)
- HTTP header injection for Gerrit (`X-Forwarded-User`)
- Nexus and Docker registry: bypass (Nexus handles its own auth)
- Ghost and Cgit remain public (no auth)
- Setup guide: `authelia/setup.md`
- IMPORTANT: access_control rules are evaluated top-down, first match wins — specific resource rules (netdata, cockpit) must appear BEFORE the general `swave.lol` bypass rule
### Nginx (reverse proxy)
- Dir: `nginx/`
- Compose: `nginx/docker-compose.yaml`
- Network: joins `git-network` (IP 172.22.0.254), `ghost_network`, `jenkins_network`, `monitoring_network`, `nexus_network`, `authelia_network`
- Ports: 80 (HTTP, redirects to HTTPS), 443 (HTTPS)
- Config mounted from `/var/nginx/conf/` on host
- SSL: Let's Encrypt certs via `run_certbot.sh`, dhparam at `/var/dh_param/`
- SSL protocols: TLSv1.2 + TLSv1.3 only
- Default catch-all server blocks return 404 error page for unknown subdomains
- Error page: `nginx/error-pages/unknown-host.html`
- IMPORTANT: SSL server blocks must be commented out for first run (before certs exist)
- All `/_authelia-auth` subrequest locations explicitly set `proxy_set_header Cookie $http_cookie` to ensure session cookies reach Authelia
## Networking
Each stack has its own Docker network. Nginx joins all of them to reverse proxy:
- `git-network` — git-server + cgit + gerrit + nginx
- `ghost_network` — ghost + ghost-db + nginx
- `jenkins_network` — jenkins + git-server + gerrit + nginx
- `monitoring_network` — netdata + nginx
- `nexus_network` — nexus + nginx
- `authelia_network` — authelia + authelia-redis + nginx
## Startup Order
1. Portainer (standalone)
2. Git Server (creates git-network)
3. Ghost (creates ghost_network)
4. Jenkins (creates jenkins_network)
5. Netdata (creates monitoring_network)
6. Nexus (creates nexus_network)
7. Authelia (creates authelia_network)
8. Gerrit (joins git-network + jenkins_network, both must exist)
9. Nginx (joins all networks, must be last)
## Key Files
- `doc/setup-guide.md` — full build instructions from fresh Debian
- `doc/Jenkinsfile.example` — example DooD pipeline
- `doc/Jenkinsfile.kernel-example` — example kernel build pipeline using kernel-builder image
- `doc/jenkins-cpp-build.md` — guide for building C++ apps in Jenkins with Docker agent
- `jenkins/kernel-builder/Dockerfile` — custom Docker image for building the Linux kernel
- `jenkins/init.groovy.d/clear-stuck-builds.groovy` — startup script to clean stuck pipeline executions
- `nexus/docker-compose.yaml` — Nexus Repository Manager + Docker registry
- `doc/Jenkinsfile.nexus-example` — example pipelines for uploading artifacts and Docker images to Nexus
- `authelia/setup.md` — Authelia SSO setup guide (secrets, OIDC, Gerrit HTTP auth, Jenkins OIDC)
- `authelia/nginx-authelia.conf` — nginx server block snippet for auth.swave.lol
- `authelia/config/configuration.yml` — Authelia config (OIDC_RSA_KEY_PLACEHOLDER replaced by awk on server)
- `authelia/config/users_database.yml.example` — user database template (real file gitignored)
- `git-server/gerrit.config.example` — Gerrit config template (real file gitignored, lives at `/var/gerrit/etc/gerrit.config`)
- `ghost/.env.example` — template for Ghost env vars
- `doc/git-performance.md` — git server performance tuning guide
- `nginx/error-pages/unknown-host.html` — 404 page for unknown subdomains
- `.gitignore` — excludes `.env` files and other secrets
## Conventions
- Each service lives in its own directory with its own compose file
- Compose files are named `docker-compose.yaml` or `compose.yml` or `server.yaml` (inconsistent, historical)
- Environment variables with secrets go in `.env`/`stack.env` files (gitignored)
- Nginx config is a single `nginx.conf` with multiple server blocks
- All services use `restart: always`
## TODOs (from Readme.md)
- Automatic certificate renewal for Let's Encrypt
- Better organization of nginx configs (split per service?)
- ~~Cgit web interface for git repos~~ (done)
- ~~Nexus~~ (done)
- Firewall rules (allow only 80, 443, 22, 8080, 8000, 9000, 9418, 50000)
|