Skip to content

API with sidecar

Build a production-grade deployment from scratch:

  • An API container listening on 8080
  • An nginx sidecar on 80 handling TLS termination at the service boundary and serving static assets
  • Database credentials loaded from a sealed secret
  • Ingress with automatic certificate via cert-manager
  • Horizontal pod autoscaler
api.kdef
deployment "api" {
namespace = "production"
image_pull_secrets = ["regcred"]
service_account = "api"
container "api" {
image = image("api")
image_pull_policy = "IfNotPresent"
port "8080" "http" {
health = "/health"
ready = "/ready"
}
env {
APP_ENV = var.environment
DATABASE_URL = secret("db-credentials", "DATABASE_URL")
JWT_SECRET = secret("jwt-keys", "secret")
LOG_LEVEL = var.environment == "production" ? "warn" : "debug"
}
env_from {
config_map = "api-config"
}
resources {
cpu = "300m..1000m"
memory = "512Mi..1Gi"
}
volume "assets" {
mount_path = "/app/public"
empty_dir = true
}
security_context {
run_as_user = 1000
run_as_non_root = true
read_only_root = true
}
}
container "nginx" {
image = image("nginx")
port "80" "http" {
tcp_health = true
}
resources {
cpu = "50m..200m"
memory = "32Mi..128Mi"
}
volume "nginx-conf" {
mount_path = "/etc/nginx/conf.d/default.conf"
sub_path = "default.conf"
config_map = "nginx-config"
}
volume "assets" {
mount_path = "/var/www/html"
empty_dir = true
read_only = true
}
}
init "warmup" {
image = image("api")
command = ["/bin/sh", "-c", "php bin/console cache:warmup && cp -R public/* /shared/"]
volume "assets" {
mount_path = "/shared"
empty_dir = true
}
}
service {
port "80" "http" {}
}
ingress {
host = "api.example.com"
tls = true
issuer = "letsencrypt-production"
annotations = {
"nginx.ingress.kubernetes.io" = {
"proxy-body-size" = "50m"
"proxy-read-timeout" = "120"
}
}
}
autoscale {
min = 2
max = 10
cpu = 70
memory = 80
}
}

One kdef render --dir k8s/ produces, in order:

  1. Deployment api with init container + two sidecars
  2. Service api on port 80 → containerPort 80 (the nginx sidecar)
  3. Ingress api.example.com with TLS and cert-manager annotations
  4. Certificate (via cert-manager) for the host
  5. HorizontalPodAutoscaler targeting 70% CPU / 80% memory, 2–10 replicas

Five resources. One block.

vars.kdef
variable "environment" {
type = "enum[staging, production]"
default = "staging"
}
ingress_defaults {
issuer = "letsencrypt-production"
annotations = {
"nginx.ingress.kubernetes.io" = {
"force-ssl-redirect" = "true"
}
}
}
images.kdef
images {
api = "registry.example.com/my-app/api:1.4.2"
nginx = "registry.example.com/nginx:stable-alpine"
}
configmaps.kdef
configmap "api-config" {
namespace = "production"
data = {
APP_NAME = "my-api"
APP_URL = "https://api.example.com"
}
}
configmap "nginx-config" {
namespace = "production"
data = {
"default.conf" = file("configs/nginx.conf")
}
}
# secrets.kdef — committed safely; values are kubeseal-encrypted
sealedsecret "db-credentials" {
namespace = "production"
data = {
DATABASE_URL = "AgBy3i4OJSWK+PiTySYZZA9rO43..."
}
}
sealedsecret "jwt-keys" {
namespace = "production"
data = {
secret = "AgCE9F2h7GKJF8mL3nP5rS7tV9xB..."
}
}

To produce those encrypted blobs:

Terminal window
kdef seal --secret db-credentials --key DATABASE_URL \
--value "postgres://api:hunter2@db:5432/api"

See the sealed secrets guide for the full flow.

Terminal window
kdef validate --dir k8s/
kdef diff --dir k8s/
kdef apply --dir k8s/

Or wire it up for GitOps with Flux or ArgoCD.