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