Skip to main content

DigitalOcean Deployment

Ever Works production runs on DigitalOcean Kubernetes (DOKS) with a managed PostgreSQL database and container registry. The deployment is automated through GitHub Actions.

Infrastructure Components

ComponentDigitalOcean ServiceDetails
ComputeKubernetes (DOKS)Cluster k8s-gauzy in sfo2 region
DatabaseManaged PostgreSQLSSL/TLS with CA certificate
Container RegistryDO Container Registryregistry.digitalocean.com/ever/
DNS / DomainsDigitalOcean DNSapi.ever.works, app.ever.works

Kubernetes Cluster

The production cluster is named k8s-gauzy and runs in the sfo2 (San Francisco) region. Access is managed through doctl with short-lived credentials:

doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-gauzy

The 600-second (10-minute) credential expiry limits the window of access during deployments.

Deployments

Two Kubernetes Deployments run the platform:

DeploymentImagePort
ever-works-apighcr.io/ever-works/ever-works-api:latest3100
ever-works-webghcr.io/ever-works/ever-works-web:latest3000

Ingress & TLS

The cluster uses Kubernetes Ingress with TLS termination. Two TLS secrets manage certificates:

DomainTLS Secret
api.ever.worksapi.ever.works-tls
app.ever.worksapp.ever.works-tls

Certificates are stored as GitHub Secrets (base64-encoded) and created/updated during deployment:

kubectl create secret tls api.ever.works-tls \
--save-config --dry-run=client \
--cert=${HOME}/ingress.api.crt \
--key=${HOME}/ingress.api.key \
-o yaml | kubectl --context do-sfo2-k8s-gauzy apply -f -

The --save-config --dry-run=client pattern enables idempotent create-or-update.

K8s Manifests

Kubernetes manifests live in .deploy/k8s/ and use environment variable substitution:

envsubst < .deploy/k8s/k8s-manifest.prod.yaml | kubectl apply -f -

The envsubst command replaces ${VARIABLE} placeholders in the manifest with actual values from GitHub Secrets. This covers all application configuration: database, auth, plugins, mail, and monitoring.

Container Registry

Images are pushed to three registries during CI, with DigitalOcean serving as one of the deployment sources:

RegistryImage Pattern
GHCR (primary)ghcr.io/ever-works/ever-works-api:latest
Docker Hubeverco/ever-works-api:latest
DigitalOceanregistry.digitalocean.com/ever/ever-works-api:latest

Registry authentication uses doctl registry login with short-lived (3600s) credentials.

Managed PostgreSQL

The production database is a DigitalOcean Managed PostgreSQL instance with SSL/TLS required.

SSL Configuration

The database CA certificate is provided as a base64-encoded GitHub Secret (DATABASE_CA_CERT). During deployment, it is decoded and written to a file:

echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt

The application connects using:

VariableValue
DATABASE_TYPEpostgres
DATABASE_URLConnection string with SSL
DATABASE_SSL_MODEtrue
DATABASE_CA_CERTBase64-encoded CA certificate

The database configuration in database.config.ts processes SSL options:

if (config.database.sslMode()) {
baseConfig.ssl = getTlsOptions(true, config.database.databaseCaCert());
}

Deployment Flow

GitHub Actions (deploy-do-prod.yml)
|
+-- Install doctl
|
+-- Save kubeconfig (600s TTL)
|
+-- Write PostgreSQL CA cert
|
+-- Generate TLS secrets for Ingress
|
+-- envsubst + kubectl apply (K8s manifests)
|
+-- kubectl rollout restart (API + Web deployments)

Environment Variables in Production

The deployment workflow injects a comprehensive set of environment variables into the K8s manifests:

CategoryKey Variables
ApplicationWEB_URL, ALLOWED_ORIGINS
AuthJWT_SECRET, GH_CLIENT_ID, GH_CLIENT_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
OAuth CallbacksGH_CALLBACK_URL, GOOGLE_CALLBACK_URL
DatabaseDATABASE_TYPE, DATABASE_URL, DATABASE_HOST, DATABASE_PORT, DATABASE_SSL_MODE, DATABASE_CA_CERT
Trigger.devTRIGGER_ENABLED, TRIGGER_SECRET_KEY, TRIGGER_INTERNAL_SECRET
PluginsPLUGIN_OPENROUTER_API_KEY, PLUGIN_GITHUB_CLIENT_ID, PLUGIN_TAVILY_API_KEY, PLUGIN_SCREENSHOTONE_ACCESS_KEY
MailMAILER_PROVIDER, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, RESEND_APIKEY

Rollout Strategy

The current deployment strategy uses :latest tag with rolling restarts:

kubectl rollout restart deployment/ever-works-api
kubectl rollout restart deployment/ever-works-web

This triggers Kubernetes to recreate pods, pulling the latest image from the registry. Kubernetes handles rolling updates to maintain zero-downtime deployments.

Access Control

CredentialScopeLifetime
DIGITALOCEAN_ACCESS_TOKENdoctl API accessLong-lived (GitHub Secret)
kubeconfig credentialsCluster access600 seconds
Registry loginImage pull/push3600 seconds
TLS certificatesIngress terminationUntil manual rotation