Your first deployment
This page takes one application from nothing to running on your server, then updates it, then breaks it on purpose. It assumes a server with Shipwick installed and shipwick installed and logged in.
You need a Docker image the server can pull. The examples use ghcr.io/company/my-api, which listens on port 8080 and answers GET /health. For an image in a private registry, see Pull from private registries first.
Create deploy.yaml
In your application's repository:
shipwick initinit asks for the application name (the default is the directory name), the image, the port the application listens on, and a public domain. Port and domain are optional. With --image it never prompts, which suits scripts:
shipwick init --name my-api --image ghcr.io/company/my-api:1.4.1 --port 8080 --domain api.example.com✓ Created deploy.yaml
Review it, then run: shipwick deployinit refuses to overwrite an existing deploy.yaml unless you pass --force.
Review deploy.yaml
The file contains your answers as live settings and everything else as commented-out examples:
name: my-api
# Pin a version tag: deployments are recorded (and rolled back) by it.
image: ghcr.io/company/my-api:1.4.1
# The port your application listens on inside the container.
port: 8080
# Served over HTTPS automatically.
domain: api.example.com
replicas: 1
# env:
# DATABASE_URL: postgres://user:password@host:5432/db
# A replica receives traffic only once this endpoint answers 2xx.
# health:
# path: /health
# interval: 10s
# timeout: 3s
# retries: 3
# Per-replica limits. Unlimited when omitted.
# resources:
# cpu: 1
# memory: 512mb
restart:
policy: always # always | on-failure | neverOnly name and image are required. The image's tag becomes the deployment's version, so pin a version tag rather than latest.
For this walk-through, set replicas: 2 and uncomment the health block. Two replicas make the rolling update visible, and a health check is what lets Shipwick tell a working version from a broken one. Without a health block, a deployment only verifies that replicas start and stay up for a few seconds.
The DNS record for domain must point at the server. Every field is described in the deploy.yaml reference.
Validate
validate checks the file offline and shows how it will be applied, defaults included:
shipwick validate✓ deploy.yaml is valid
Name my-api
Image ghcr.io/company/my-api:1.4.1
Version 1.4.1
Replicas 2
Port 8080
Domain api.example.com
Health check GET /health every 10s (timeout 3s, 3 retries)
Resources unlimited CPU, unlimited memory
Restart alwaysMistakes are reported all at once, by field:
invalid deploy.yaml
port:
invalid value 99999
expected: a number between 1 and 65535
resources.memory:
invalid value "abc"
expected: 128mb, 512mb, 1gb, ...deploy runs the same validation, and the agent validates again on its side.
Deploy
shipwick deploydeploy sends the file to the agent and waits for the result. On a first deployment there is nothing to replace, so all replicas start together:
Deploying my-api...
✓ Validated deploy.yaml
✓ Pulled image ghcr.io/company/my-api:1.4.1
✓ Started 2 containers
✓ 2 replicas passed health checks
✓ Routed https://api.example.com to 2 replicas
✓ Deployment successfulA summary follows: the version, how long the deployment took, how many replicas are healthy, and the URL. Caddy obtains the certificate for the domain on its own.
Pressing Ctrl+C while deploy waits stops the waiting, not the deployment.
Look at what is running
shipwick status # version, CPU and memory, replicas, recent deployments, supervisor events
shipwick ps # every application on the server
shipwick logs -f # follow the logs of all replicasRun in the directory that holds deploy.yaml, these commands act on the application named in it. Elsewhere, name the application: shipwick status my-api. More in Inspect applications and read logs.
Deploy a new version
Change the image tag in deploy.yaml to 1.4.2 and deploy again:
shipwick deployDeploying my-api...
✓ Validated deploy.yaml
✓ Pulled image ghcr.io/company/my-api:1.4.2
✓ Started 1 container
✓ Replica 1 passed health checks
✓ Replica 1/2 is serving 1.4.2; its 1.4.1 predecessor is retired
✓ Replica 2 passed health checks
✓ Replica 2/2 is serving 1.4.2; its 1.4.1 predecessor is retired
✓ Routed https://api.example.com to 2 replicas
✓ Deployment successful
my-api 1.4.2 deployed in 6.1s
2/2 replicas healthy
https://api.example.comThis is a rolling update. Replicas are replaced one at a time. Each new replica must pass its health check before it joins the rotation, and the old replica it replaces leaves the rotation before it is stopped. There is never more than one container above the desired count, and serving capacity never drops below it.
For a moment both versions serve side by side, so the two must be able to coexist. Database migrations, above all, must be backward compatible. Deployments explains the mechanism in full.
If logs -f was running, it ends when the old containers are replaced. Run it again to follow the new ones.
When a deployment fails
Suppose the next version cannot start, because it needs an environment variable nobody set. deploy tells you why it failed, and whether users were affected:
✗ Deployment failed
replica 1 exited with code 1 shortly after start
Last output of replica 1:
panic: DATABASE_URL is not set
my-api is still running 1.4.2; the failed deployment did not affect it.shipwick deploy exits with status 1. The failed replica's last log lines are saved with the deployment, the new containers are removed, and the old version keeps serving.
What happens next depends on how far the rollout got:
- If the first new replica failed — the usual case — nothing of the old version was touched. The deployment is
FAILED. - If some old replicas had already been replaced, they are recreated from the previous deployment's stored configuration, verified, and given traffic again. The deployment is
ROLLED_BACK, and the command reportsDeployment failed and was rolled back.
A replica that never answers its health check fails the deployment the same way:
✗ Deployment failed
replica 1 did not become healthy within 30s: GET /health on port 8080: connection refusedA new replica has interval × retries to answer — 30 seconds by default. Raise retries for an application that starts slowly.
Every attempt, failed or not, is kept in the history that shipwick status shows. Only one deployment per application runs at a time; a second is refused rather than queued.
What's next
- Deploy from CI with
shipwick deploy --image. - Roll back to an earlier version.
- Concepts: Deployments, Health and supervision, Routing and HTTPS.
- Reference: deploy.yaml, shipwick.