What you'll build
MultiJuicer lets you run one Juice Shop instance per team behind a single URL, so a whole class or CTF group can hack their own copy without spinning up servers by hand. By the end you'll have a managed Kubernetes cluster on DigitalOcean, MultiJuicer installed via Helm, a public HTTPS URL on your own domain, and an admin dashboard to watch every team.
Time required: ~30–45 minutes (most of it waiting for the cluster to provision).
| Resource | Approx. monthly cost |
|---|---|
| Kubernetes control plane | Free (standard) |
3× s-2vcpu-4gb worker nodes | ~$48 |
| Regional LoadBalancer | ~$12 |
The tools
- doctl — DigitalOcean's CLI; talks to your account to create and delete resources.
- kubectl — the standard tool for talking to any Kubernetes cluster.
- helm — a package manager for Kubernetes; installs MultiJuicer in one command.
Prerequisites
1. DigitalOcean account + API token
In the DigitalOcean console go to API → Tokens → Generate New Token (full access). Copy it — you only see it once, and you can revoke it when finished.
2. Install the CLI tools
macOS (Homebrew):
brew install doctl kubectl helm
Linux: download each binary from its release page (doctl, kubectl, helm), then verify:
doctl version
kubectl version --client
helm version
3. Authenticate doctl
doctl auth init # paste your API token
doctl account get # confirms your account email
Step 1 Create the Kubernetes cluster
One command creates a managed cluster. Important: the default node size (s-1vcpu-2gb) is too small for MultiJuicer — start with s-2vcpu-4gb to avoid a rebuild.
doctl kubernetes cluster create juicy-k8s --size s-2vcpu-4gb
This takes about 5–7 minutes; doctl then points kubectl at the new cluster automatically. Verify:
kubectl config current-context # do-nyc1-juicy-k8s
kubectl get nodes # 3 nodes, all Ready
Step 2 Install MultiJuicer with Helm
helm install multi-juicer oci://ghcr.io/juice-shop/multi-juicer/helm/multi-juicer
Watch the pod start, and wait until it shows 1/1 Running — that's the MultiJuicer "balancer", the brain that hands each team its own Juice Shop.
kubectl get pods
# multi-juicer-58c4746bbd-g4xgq 1/1 Running
Pending. If kubectl describe pod multi-juicer-<suffix> shows "0/3 nodes are available: 3 Insufficient cpu", your nodes are too small. The pod requests 400m CPU; s-1vcpu-2gb nodes lack the headroom after system pods. Recreate the cluster with --size s-2vcpu-4gb and re-run the Helm install (which is why Step 1 uses the larger size).
Step 3 Verify locally & log in as admin
Before exposing it, test with a port-forward — a temporary tunnel from your machine to the cluster:
kubectl port-forward service/multi-juicer 8080:8080
Open http://localhost:8080, create a team, and click Start Hacking to get a personal Juice Shop. The admin dashboard lives at /balancer (log in as team admin). The admin password is auto-generated and stored in a Kubernetes secret:
kubectl get secrets multi-juicer-secret \
-o=jsonpath='{.data.adminPassword}' | base64 --decode
Step 4 Expose it on your domain
A port-forward only works on your machine. To give teams a real URL, add a LoadBalancer — a DigitalOcean appliance with a public IP that routes traffic into the cluster. How you handle HTTPS depends on where your DNS lives.
Path A — Domain managed by DigitalOcean
DigitalOcean can issue a free Let's Encrypt certificate and terminate HTTPS on the LoadBalancer:
doctl compute certificate create --type lets_encrypt \
--name multijuicer-cert --dns-names mj.yourdomain.com
doctl compute certificate list # note the cert ID
wget https://raw.githubusercontent.com/juice-shop/multi-juicer/main/guides/digital-ocean/do-lb.yaml
# paste the cert ID into the do-loadbalancer-certificate-id annotation
kubectl create -f do-lb.yaml
Then add an A record for mj.yourdomain.com pointing at the LoadBalancer's public IP.
Path B — Domain managed by Cloudflare
Proxy the hostname through Cloudflare (trusted HTTPS at the edge) and put TLS on the origin LoadBalancer. Cloudflare's default Full SSL mode connects to the origin over HTTPS and accepts any certificate — including self-signed — so a self-signed cert is enough and you don't change any zone-wide setting.
1. Create a self-signed origin certificate (key stays local):
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
-keyout key.pem -out cert.pem \
-subj "/CN=mj.yourdomain.com" \
-addext "subjectAltName=DNS:mj.yourdomain.com"
2. Upload it to DigitalOcean and note the ID:
doctl compute certificate create --type custom --name mj-selfsigned \
--leaf-certificate-path cert.pem --private-key-path key.pem
doctl compute certificate list
3. Create the LoadBalancer with HTTP + 443 listeners (do-lb-cloudflare-tls.yaml), pasting your cert ID into the do-loadbalancer-certificate-id annotation and listing 443 under do-loadbalancer-tls-ports. Apply it:
kubectl create -f do-lb-cloudflare-tls.yaml
kubectl get svc multi-juicer-loadbalancer -w # wait for the EXTERNAL-IP
app.kubernetes.io/instance: multi-juicer-multi-juicer (doubled), regardless of your release name. If the LoadBalancer never routes traffic, that label mismatch is the usual cause.
4. In Cloudflare, add an A record for mj pointing at the LoadBalancer IP, set to Proxied (orange cloud). Verify:
curl -I https://mj.yourdomain.com/ # expect HTTP 200 over HTTPS
Optional — gate your workshop with Cloudflare Access
For a private workshop, add a Cloudflare Access (Zero Trust) self-hosted application for the hostname with a one-time-PIN policy. Visitors then authenticate before the app loads: Visit URL → Enter email → Get code → MultiJuicer. (Access requires the record to be Proxied.)
Step 5 Run a workshop
Share your URL. Each participant (after the Access login, if enabled) enters a team name, joins, and clicks Start Hacking to get their own isolated Juice Shop. You watch progress from the admin scoreboard at /balancer. Idle instances are put to sleep automatically to save resources.
Step 6 Tear it down
helm delete multi-juicer
kubectl delete -f do-lb-cloudflare-tls.yaml # or do-lb.yaml for Path A
doctl kubernetes cluster delete juicy-k8s
doctl compute certificate delete <cert-id>
Finally remove the DNS record, delete the Cloudflare Access application if you created one, and confirm in the console that no clusters or load balancers remain.