Skip to content

Deploy a web application

Goal

An application of yours running behind a hostname, with TLS, and replaced without downtime when you change it.

Before you start

  • At least one ready node in your tenant — see enrol a node.
  • An image pushed to a registry the platform can pull from, pinned to a tag that is not latest.
  • A DNS name pointing at the routing edge.

Steps

  1. Open Deployments and choose New deployment.
  2. Give it a name, the image reference and a replica count.
  3. Add the networks it needs, and the routing hostname and port under ingress.
  4. Add a health check of type exec with a command the image can actually run.
  5. Choose Deploy, and watch it reach running.
The Deployments list in the Odysseus dashboard, showing each deployment with its stack, image reference, replica count and running status, and the New deployment button used in step 1.
apiVersion: odysseus/v1
kind: Deployment
metadata:
name: api
labels:
app: api
spec:
image: registry.delta-telematics.ca/acme/api:1.4.2
replicas: 3
environment:
LOG_LEVEL: info
networks: [acme-network, traefik-public]
resources:
limits: {cpu: "1", memory: 512Mi}
requests: {cpu: 250m, memory: 256Mi}
healthCheck: # required for rolling
type: exec
command: "wget -qO- http://localhost:8080/healthz"
interval: 10s
timeout: 15s
retries: 3
startPeriod: 20s
secrets:
- name: DB
vaultPath: deployments/api/db
dependsOn:
- job: api-migrate
condition: complete
placement:
affinity: {label: role, value: app}
onNodeFailure: reschedule
ingress:
host: api.example.com
port: 8080
tls: {enabled: true, certResolver: letsencrypt}
healthCheck: {path: /healthz, interval: 10s, timeout: 3s} # LB probe — the zero-downtime part
compress: true
headers:
response:
set: {X-Frame-Options: DENY}
retry: {attempts: 3, initialInterval: 100ms}
update:
strategy: rolling
allow_concurrent_versions: true # required attestation
max_surge: 1
max_unavailable: 0
health_timeout: 90s
min_healthy_time: 15s
progress_deadline: 10m
failure_action: rollback
Validated against Manifest · testdata/docs-examples/routed-deployment.yaml

Verify

The deployment reports its full replica count running, and the hostname serves your application over HTTPS.

Evidence this worked

GET /api/v1/deployments/{name} shows the running replica count and the image you sent. The dashboard’s logs view shows the application’s own start-up output — a container that starts and exits immediately shows the start line and nothing else.

When it fails

  • The image is refused. The rejection asks for a pinned reference, e.g. “nginx:1.27” — never :latest, because there is nothing to run without one. Pin the tag.
  • The health check is refused. Only exec produces a container health check, and no other type produces a container healthcheck — see health checks and process reaping.
  • A rolling update is refused. Gradual rollouts have preconditions, and each refusal names the one you missed: see rollouts.
  • Every code, with the field it names and the accepted form: rejections and alterations.