Skip to content

Instantly share code, notes, and snippets.

@Jordan-Hall
Last active October 14, 2025 23:19
Show Gist options
  • Select an option

  • Save Jordan-Hall/62f8c5f1f21f3165325aaca47e241d5d to your computer and use it in GitHub Desktop.

Select an option

Save Jordan-Hall/62f8c5f1f21f3165325aaca47e241d5d to your computer and use it in GitHub Desktop.
#!/bin/bash
set -e
# -------------------------
# CONFIGURATION
# -------------------------
HOST_IP=$(hostname -I | awk '{print $1}')
SSLIP_DOMAIN="$HOST_IP.sslip.io"
EMAIL="jordan@libertyware.co.uk" # replace with real email
SURREALDB_REPLICAS=3
COOLIFY_REPLICAS=1
KUBERO_REPLICAS=1
PD_REPLICAS=3
TIKV_REPLICAS=3
STORAGE_PATH="/mnt/tikv-data"
K3S_MASTER_TOKEN_FILE="/root/k3s_token.txt"
K3S_MASTER_IP_FILE="/root/k3s_master_ip.txt"
# -------------------------
# SYSTEM UPDATE & FIREWALL
# -------------------------
echo "Updating system..."
sudo apt update -y && sudo apt upgrade -y
sudo apt install -y curl wget gnupg lsb-release ufw software-properties-common apt-transport-https
# Setup firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 6443/tcp
sudo ufw allow 80,443/tcp
sudo ufw --force enable
# -------------------------
# DOCKER INSTALLATION
# -------------------------
if ! command -v docker >/dev/null 2>&1; then
echo "Installing Docker..."
sudo apt remove -y docker docker-engine docker.io containerd runc || true
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /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" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update -y
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable docker
sudo systemctl start docker
fi
# -------------------------
# K3S INSTALL FUNCTIONS
# -------------------------
install_k3s_master() {
echo "Installing k3s master..."
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644" sh -
sleep 10
echo $(sudo cat /var/lib/rancher/k3s/server/node-token) > $K3S_MASTER_TOKEN_FILE
echo $HOST_IP > $K3S_MASTER_IP_FILE
}
install_k3s_agent() {
local master_ip="$1"
local token="$2"
echo "Installing k3s agent..."
curl -sfL https://get.k3s.io | K3S_URL="https://$master_ip:6443" K3S_TOKEN="$token" sh -
}
wait_nodes_ready() {
echo "Waiting for all nodes to be ready..."
kubectl wait --for=condition=Ready nodes --all --timeout=180s
}
# -------------------------
# INSTALL K3S
# -------------------------
if [ -f /etc/rancher/k3s/k3s.yaml ]; then
echo "k3s already installed."
else
if [ ! -f $K3S_MASTER_TOKEN_FILE ]; then
install_k3s_master
else
MASTER_IP=$(cat $K3S_MASTER_IP_FILE)
K3S_TOKEN=$(cat $K3S_MASTER_TOKEN_FILE)
install_k3s_agent $MASTER_IP $K3S_TOKEN
fi
fi
wait_nodes_ready
# -------------------------
# CREATE NAMESPACES
# -------------------------
for ns in tikv surrealdb coolify kubero cert-manager monitoring; do
kubectl create namespace $ns --dry-run=client -o yaml | kubectl apply -f -
done
# -------------------------
# INSTALL INGRESS-NGINX
# -------------------------
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/k3s/deploy.yaml
# -------------------------
# INSTALL CERT-MANAGER
# -------------------------
kubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.crds.yaml
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl wait --for=condition=Ready pods --all -n cert-manager --timeout=180s
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-sslip
spec:
acme:
email: $EMAIL
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-sslip-key
solvers:
- http01:
ingress:
class: nginx
EOF
# -------------------------
# DEPLOY PD (Placement Driver)
# -------------------------
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pd
namespace: tikv
spec:
serviceName: pd
replicas: $PD_REPLICAS
selector:
matchLabels:
app: pd
template:
metadata:
labels:
app: pd
spec:
containers:
- name: pd
image: pingcap/pd:latest
args:
- --name=pd
- --data-dir=/pd/data
- --client-urls=http://0.0.0.0:2379
- --peer-urls=http://0.0.0.0:2380
- --initial-cluster=pd-0=http://pd-0.pd.tikv.svc.cluster.local:2380,pd-1=http://pd-1.pd.tikv.svc.cluster.local:2380,pd-2=http://pd-2.pd.tikv.svc.cluster.local:2380
ports:
- containerPort: 2379
- containerPort: 2380
volumeMounts:
- name: pd-data
mountPath: /pd/data
volumeClaimTemplates:
- metadata:
name: pd-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
name: pd
namespace: tikv
spec:
clusterIP: None
selector:
app: pd
ports:
- port: 2379
targetPort: 2379
EOF
# -------------------------
# DEPLOY TiKV
# -------------------------
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: tikv
namespace: tikv
spec:
serviceName: tikv
replicas: $TIKV_REPLICAS
selector:
matchLabels:
app: tikv
template:
metadata:
labels:
app: tikv
spec:
containers:
- name: tikv
image: pingcap/tikv:latest
args:
- --pd=pd.tikv.svc.cluster.local:2379
- --addr=0.0.0.0:20160
- --data-dir=/tikv/data
ports:
- containerPort: 20160
volumeMounts:
- name: tikv-data
mountPath: /tikv/data
volumeClaimTemplates:
- metadata:
name: tikv-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: tikv
namespace: tikv
spec:
clusterIP: None
selector:
app: tikv
ports:
- port: 20160
targetPort: 20160
EOF
# -------------------------
# DEPLOY SURREALDB
# -------------------------
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: surrealdb
namespace: surrealdb
spec:
replicas: $SURREALDB_REPLICAS
selector:
matchLabels:
app: surrealdb
template:
metadata:
labels:
app: surrealdb
spec:
containers:
- name: surrealdb
image: surrealdb/surrealdb:latest
args:
- "--log"
- "info"
- "--bind"
- "0.0.0.0:8000"
- "--store"
- "tikv://pd.tikv.svc.cluster.local:2379"
ports:
- containerPort: 8000
EOF
# -------------------------
# DEPLOY COOLIFY
# -------------------------
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: coolify
namespace: coolify
spec:
replicas: $COOLIFY_REPLICAS
selector:
matchLabels:
app: coolify
template:
metadata:
labels:
app: coolify
spec:
containers:
- name: coolify
image: coollabsio/coolify:latest
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: coolify
namespace: coolify
spec:
selector:
app: coolify
ports:
- port: 80
targetPort: 3000
EOF
# -------------------------
# DEPLOY KUBERO (DASHBOARD + OPTIONAL MODULES)
# -------------------------
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubero
namespace: kubero
spec:
replicas: $KUBERO_REPLICAS
selector:
matchLabels:
app: kubero
template:
metadata:
labels:
app: kubero
spec:
containers:
- name: kubero
image: kubero-dev/kubero:latest
ports:
- containerPort: 8080
- name: kubero-dashboard
image: kubero-dev/kubero-dashboard:latest
ports:
- containerPort: 9090
EOF
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: kubero
namespace: kubero
spec:
selector:
app: kubero
ports:
- port: 8080
targetPort: 8080
EOF
# -------------------------
# DEPLOY PROMETHEUS + GRAFANA
# -------------------------
kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/raw/main/bundle.yaml
kubectl wait --for=condition=Ready pods --all -n monitoring --timeout=180s
# Simple Prometheus instance
cat <<EOF | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: tikv-prometheus
namespace: monitoring
spec:
serviceAccountName: prometheus
resources:
requests:
memory: 400Mi
EOF
# -------------------------
# TLS-ENABLED INGRESS
# -------------------------
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-platform-ingress
namespace: coolify
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-sslip"
spec:
tls:
- hosts:
- coolify.$SSLIP_DOMAIN
- surrealdb.$SSLIP_DOMAIN
- kubero.$SSLIP_DOMAIN
secretName: multi-platform-tls
rules:
- host: coolify.$SSLIP_DOMAIN
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: coolify
port:
number: 80
- host: surrealdb.$SSLIP_DOMAIN
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: surrealdb
port:
number: 8000
- host: kubero.$SSLIP_DOMAIN
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubero
port:
number: 8080
EOF
echo "✅ Full production-ready cluster installed!"
echo "Coolify: https://coolify.$SSLIP_DOMAIN"
echo "SurrealDB: https://surrealdb.$SSLIP_DOMAIN"
echo "Kubero Dashboard: https://kubero.$SSLIP_DOMAIN"
echo "Grafana: https://grafana.$SSLIP_DOMAIN"
echo "🎉 Multi-node ready — copy k3s_token.txt & k3s_master_ip.txt to new nodes to join agents."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment