Skip to content

Deployment Guide — DigitalOcean (Dev)

Minimal dev deployment on DigitalOcean: DOKS (Kubernetes) + Managed Postgres + Redpanda + Redis.

Migration note: DigitalOcean is the temporary development platform, chosen for quick setup and low entry cost. Production will run on Hetzner (dedicated servers, better price-to-performance at scale). The Hetzner Terraform provider is already referenced in infrastructure/setup.tf. When migrating: - K8s manifests (infrastructure/cloud_environment_setup/digitalocean/k8s/) are portable — they work on any K8s cluster - Bitbucket Pipelines need only a registry URL and kubeconfig swap - Terraform (infrastructure/cloud_environment_setup/digitalocean/terraform/) is DO-specific and will be replaced with Hetzner equivalents (hcloud provider + kube-hetzner or similar)


Monthly cost

Component Spec Cost
DOKS control plane Managed Free
Worker nodes 2× s-2vcpu-4gb $48/mo
Managed Postgres db-s-1vcpu-1gb $15/mo
Container Registry Starter (500 MB) Free
Total ~$63/mo

Add $12/mo for a DigitalOcean Load Balancer if you want a proper ingress with TLS. Otherwise use NodePort + Cloudflare proxy.


Prerequisites

Install these locally:


1. Provision infrastructure (Terraform)

cd infrastructure/cloud_environment_setup/digitalocean/terraform

# First time only
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars — set your do_token

terraform init
terraform plan     # review what will be created
terraform apply    # provision DOKS + Postgres + registry

Save the outputs — you'll need them for K8s config:

terraform output db_host
terraform output db_port
terraform output db_password   # sensitive — use -json flag
terraform output cluster_name
terraform output registry_name

2. Configure kubectl

doctl kubernetes cluster kubeconfig save $(terraform output -raw cluster_name)
kubectl get nodes   # verify connection

3. Connect registry to cluster

doctl registry kubernetes-manifest | kubectl apply -f -

This creates an image pull secret so the cluster can pull from your private registry.


4. Deploy supporting services

cd ../k8s

# Create namespace
kubectl apply -f namespace.yml

# Deploy Redpanda (Kafka) and Redis
kubectl apply -f redpanda.yml
kubectl apply -f redis.yml

# Wait for them to be ready
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=redpanda -n smartsapp-dev --timeout=120s
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=redis -n smartsapp-dev --timeout=60s

5. Configure and deploy the app

Update ConfigMap with DB connection

Edit app-configmap.yml — replace the SPRING_DATASOURCE_URL with your actual DB host and port from Terraform outputs:

# Get values
DB_HOST=$(cd ../../terraform && terraform output -raw db_host)
DB_PORT=$(cd ../../terraform && terraform output -raw db_port)

# macOS sed
sed -i '' "s/REPLACE_DB_HOST/$DB_HOST/g; s/REPLACE_DB_PORT/$DB_PORT/g" app-configmap.yml

Create the secret

cp app-secret.yml.example app-secret.yml

# Get password from Terraform
DB_PASS=$(cd ../../terraform && terraform output -raw db_password)

# macOS sed
sed -i '' "s/REPLACE_WITH_DB_PASSWORD/$DB_PASS/" app-secret.yml

Apply everything

kubectl apply -f app-configmap.yml
kubectl apply -f app-secret.yml
kubectl apply -f app-deployment.yml
kubectl apply -f app-service.yml

6. Access the app

Option A: Port-forward (quickest, no load balancer needed)

kubectl port-forward svc/system-app 8080:80 -n smartsapp-dev

Then visit: - API: http://localhost:8080 - Swagger UI: http://localhost:8080/swagger-ui.html - Health: http://localhost:8080/actuator/health

Option B: Ingress with nginx (requires load balancer — +$12/mo)

# Install nginx ingress controller
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/do/deploy.yaml

# Wait for the load balancer IP
kubectl get svc ingress-nginx-controller -n ingress-nginx -w

# Apply ingress rule (edit the host in app-ingress.yml first)
kubectl apply -f app-ingress.yml

7. Push an image manually (first deploy before Bitbucket is set up)

# Build locally
cd backend
podman build -t registry.digitalocean.com/smartsappregistry/system:latest -f Containerfile .

# Authenticate
doctl registry login

# Push
podman push registry.digitalocean.com/smartsappregistry/system:latest

# Restart the deployment to pull the new image
kubectl rollout restart deployment/system-app -n smartsapp-dev

8. Set up Bitbucket Pipelines

Add these Repository Variables in Bitbucket (Settings → Pipelines → Repository variables):

Variable Value Secured
DIGITALOCEAN_ACCESS_TOKEN Your DO API token Yes
REGISTRY_NAME smartsappregistry (from Terraform output) No
K8S_CLUSTER_NAME Cluster name (from Terraform output) No

Enable Pipelines in the repository settings. Pushes to main will now auto-build, push, and deploy.


Viewing logs

# App logs
kubectl logs -f deployment/system-app -n smartsapp-dev

# Redpanda logs
kubectl logs -f statefulset/redpanda -n smartsapp-dev

# All pods
kubectl get pods -n smartsapp-dev

Tear down

# Remove K8s resources
kubectl delete namespace smartsapp-dev

# Destroy infrastructure
cd infrastructure/cloud_environment_setup/digitalocean/terraform
terraform destroy