Purpose: Provision a new Ubuntu 24.04 VPS with the core services needed for collaborative development planning — Wiki.js (knowledge base), Forgejo (git hosting), Keycloak (SSO), and supporting infrastructure.
Status: 🟡 Planned / Not yet executed
Last updated: 2026-07-26
Target OS: Ubuntu 24.04 LTS
Internet ────► Nginx (443 SSL)
│
┌────────┼────────┐
▼ ▼ ▼
wiki.<d> git.<d> auth.<d>
│ │ │
▼ ▼ ▼
Wiki.js Forgejo Keycloak
│ │ │
└────────┼────────┘
▼
PostgreSQL 15 (shared)
│
┌────┴────┐
│ │
wikijs forgejo keycloak
DB DB DB
Key principles:
opencode CLI deployed for AI-assisted operationsBefore SSL certificates can be issued, the following A records must point to the VPS public IP:
| Hostname | Type | Value |
|---|---|---|
wiki.<client-domain> |
A | <VPS_PUBLIC_IP> |
git.<client-domain> |
A | <VPS_PUBLIC_IP> |
auth.<client-domain> |
A | <VPS_PUBLIC_IP> |
| Spec | Minimum | Recommended |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU |
| RAM | 4 GB | 8 GB |
| Disk | 80 GB SSD | 160 GB SSD |
| OS | Ubuntu 24.04 LTS | Ubuntu 24.04 LTS |
| Network | Public IP | Public IP + reverse DNS |
ssh root@<VPS_PUBLIC_IP>
apt update && apt upgrade -y
apt install -y \
curl wget git ufw \
nginx certbot python3-certbot-nginx \
ca-certificates gnupg lsb-release \
unattended-upgrades apt-listchanges
dpkg-reconfigure --priority=low unattended-upgrades
# Edit /etc/apt/apt.conf.d/50unattended-upgrades as needed
systemctl enable unattended-upgrades
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker --version
docker compose version
docker network create client-infra --driver bridge
mkdir -p /opt/postgres/data
docker run -d \
--name postgres \
--restart unless-stopped \
--network client-infra \
-v /opt/postgres/data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=<secure-password> \
postgres:15-alpine
docker exec -i postgres psql -U postgres <<'SQL'
CREATE DATABASE wikijs;
CREATE DATABASE forgejo;
CREATE DATABASE keycloak;
SQL
Credentials to record:
postgres (Docker DNS)5432postgres<secure-password> (store in password manager)docker run -d \
--name keycloak \
--restart unless-stopped \
--network client-infra \
-p 127.0.0.1:8443:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=<temp-admin-password> \
-e KC_DB=postgres \
-e KC_DB_URL=jdbc:postgresql://postgres:5432/keycloak \
-e KC_DB_USERNAME=postgres \
-e KC_DB_PASSWORD=<secure-password> \
-e KC_HOSTNAME=auth.<client-domain> \
-e KC_PROXY_HEADERS=xforwarded \
quay.io/keycloak/keycloak:26.6.1
https://auth.<client-domain>/admin/admin / <temp-admin-password><client-name> (e.g., acmecorp)wiki (openid-connect, confidential)forgejo (openid-connect, confidential)In the Keycloak realm, create user accounts for the planning team:
| Username | Role | |
|---|---|---|
| (team members) | (their emails) | (realm roles as needed) |
mkdir -p /opt/wikijs
Create /opt/wikijs/config.yml:
port: 3000
bindIP: 0.0.0.0
db:
type: postgres
host: postgres
port: 5432
user: postgres
pass: <secure-password>
db: wikijs
ssl: false
logLevel: info
ha: false
docker run -d \
--name wikijs \
--restart unless-stopped \
--network client-infra \
-p 127.0.0.1:3002:3000 \
-v /opt/wikijs/config.yml:/wiki/config.yml \
ghcr.io/requarks/wiki:latest
https://wiki.<client-domain>Copy the wiki-manage.py script to this VPS:
# Install dependencies
apt install -y python3-pip
pip3 install psycopg2-binary
# Deploy script (adjust DB credentials)
# See /opt/wiki-manage.py on primary VPS
mkdir -p /opt/forgejo-docker/data/{forgejo,postgres,redis}
/opt/forgejo-docker/docker-compose.yml:
services:
forgejo:
image: codeberg.org/forgejo/forgejo:14.0
container_name: forgejo
restart: always
environment:
- DB_TYPE=postgres
- DB_HOST=postgres
- DB_PORT=5432
- DB_USER=postgres
- DB_PASS=<secure-password>
- DB_NAME=forgejo
- APP_NAME=<Client-Name> Git
- ROOT_URL=https://git.<client-domain>/
- SSH_DOMAIN=git.<client-domain>
- SSH_PORT=2222
volumes:
- ./data/forgejo:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:2222"
depends_on:
redis:
condition: service_healthy
redis:
image: redis:7-alpine
container_name: forgejo_redis
restart: always
command: redis-server --appendonly yes
volumes:
- ./data/redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
docker compose -f /opt/forgejo-docker/docker-compose.yml up -d
https://git.<client-domain>Wait for DNS records to propagate, then:
certbot --nginx -d wiki.<client-domain> -d git.<client-domain> -d auth.<client-domain>
Create Nginx site configs for each subdomain. Pattern for each:
server {
listen 443 ssl;
server_name <subdomain>.<client-domain>;
ssl_certificate /etc/letsencrypt/live/<client-domain>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<client-domain>/privkey.pem;
location / {
proxy_pass http://127.0.0.1:<port>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Service-to-port mapping:
| Subdomain | Upstream Port | Service |
|---|---|---|
wiki.<domain> |
3002 | Wiki.js |
auth.<domain> |
8443 | Keycloak |
git.<domain> |
3000 | Forgejo |
systemctl enable certbot.timer
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt install -y nodejs
node --version
npm --version
npm install -g opencode-ai
opencode --version
# Expected: 1.3.9
useradd -r -s /bin/bash -m -d /opt/opencode opencode_agent_user
mkdir -p /opt/opencode/{agent,feedback,tasks}
chown -R opencode_agent_user:opcode_agent_user /opt/opencode
Deploy adapted versions of:
/opt/wiki-manage.py — Wiki.js page management via PostgreSQL/opt/kcadmin.sh — Keycloak admin CLI/opt/opencode/agent/vps_agent.md — system prompt for the opencode agent (to be completed with client-specific instructions later).
docker ps --format 'table {{.Names}}\t{{.Status}}'
Expected: postgres, keycloak, wikijs, forgejo, forgejo_redis — all Up and healthy.
https://wiki.<client-domain> in incognito windowhttps://auth.<client-domain>git clone https://git.<client-domain>/<org>/<repo>.git
# Or via SSH:
git clone ssh://git@git.<client-domain>:2222/<org>/<repo>.git
/opt/wiki-manage.py "hello-world" "Hello World" "# Hello World" "Test page"
/opt/wiki-manage.py --get "hello-world"
Edit /etc/ssh/sshd_config:
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
ufw delete allow ssh
ufw allow from <management-ip> to any port ssh
ufw reload
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
/tasks/ planning pages accessible