Deploy MultiJuicer on DigitalOcean &

A beginner's guide to running multi-tenant OWASP Juice Shop on Kubernetes — for security workshops, training, and CTFs.

Based on the official MultiJuicer DigitalOcean guide, expanded with explanations, real command output, and troubleshooting from an actual deployment (MultiJuicer 10.1.0, Kubernetes 1.36).

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).

⚠️ Cost warning. The resources here cost roughly $45–75/month if left running. DigitalOcean bills hourly, so a short test session costs only a few cents — but you must delete everything when you're done (see Step 6). Set a reminder.
ResourceApprox. monthly cost
Kubernetes control planeFree (standard)
s-2vcpu-4gb worker nodes~$48
Regional LoadBalancer~$12

The tools

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
Troubleshooting — pod stuck in 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
Selector gotcha. The chart labels the pod 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

💸 Don't skip this or you'll keep paying. Delete the LoadBalancer before the cluster so no orphaned LB is left behind.
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.


Reference: MultiJuicer repository · Official DigitalOcean guide