# Update a server

A Tarsana server runs one image, built from one spec. To update it, you change the spec, let Tarsana build the new image, create a server from it, and then remove the old server. Your servers stay small, signed and identical to their spec.

Before you start, finish [Run it on your provider](/quickstarts/run-on-your-provider). You need `TENANT`, `ACCESS_TOKEN`, `spec.json` and the ID of the server you created, in `OLD_SERVER_ID`.

## 1. Change the spec

Make your change in `spec.json`. For example, move to a newer package snapshot to pick up security updates:

```json
"snapshot_id": "20260915T000000Z"
```

Check it with [`validate`](/api/validate) before you submit:

```sh
curl -X POST "https://api.tarsana.io/v1/tenants/$TENANT/specs/validate" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT" \
  -H "Content-Type: application/json" \
  --data @spec.json
```

```json
{
  "valid": true,
  "spec_hash": "sha256:cdff062988d4722b2ff052c97ebefb8b571858c493ce47b159df73b1c88809b1",
  "problems": []
}
```

## 2. Submit it

A changed spec has a new hash, so [`submit`](/api/submit) returns a new submission ID and starts a new build:

```sh
curl -X POST "https://api.tarsana.io/v1/tenants/$TENANT/specs" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT" \
  -H "Content-Type: application/json" \
  --data @spec.json
```

```json
{
  "submission": "sha256:cdff062988d4722b2ff052c97ebefb8b571858c493ce47b159df73b1c88809b1",
  "created": true
}
```

```sh
export NEW_SUBMISSION=sha256:paste-the-rest-of-the-new-submission-value
```

## 3. Wait for the build

Ask for the [`status`](/api/status) until `pipeline.state` is `succeeded`:

```sh
curl "https://api.tarsana.io/v1/tenants/$TENANT/submissions/$NEW_SUBMISSION" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT"
```

```json
{
  "state": "accepted",
  "pipeline": {
    "job": "j-bbdde9adc372c296",
    "state": "dispatched",
    "created": true,
    "what": "A sentence about the build's progress."
  }
}
```

## 4. Create the new server

Create a server from the new submission with [`create-server`](/api/create-server). Use a new `request_id`: the same name returns the server you already created for that submission.

```sh
curl -X POST "https://api.tarsana.io/v1/tenants/$TENANT/submissions/$NEW_SUBMISSION/create-server" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT" \
  -H "Content-Type: application/json" \
  --data '{"request_id": "web-2"}'
```

```json
{
  "created": {
    "action_id": "ff97f81e3825703462ecab41c1c4ffa3",
    "idempotent_replay": false,
    "operation_id": "5000",
    "state": "provisioning",
    "target": {
      "id": "1000",
      "region": "fsn1"
    }
  }
}
```

Wait until the new server has finished its first boot and serves what it should, then move your traffic to it, for example by changing a DNS record or a load balancer target.

## 5. Remove the old server

Copy anything you still need off the old server first: this cannot be undone. Then [`decommission-server`](/api/decommission-server):

```sh
curl -X POST "https://api.tarsana.io/v1/tenants/$TENANT/providers/hetzner/servers/$OLD_SERVER_ID/decommission" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: $TENANT"
```

```json
{
  "decommissioned": {
    "state": "destroyed",
    "observed": "not-found",
    "action_id": "ff97f81e3825703462ecab41c1c4ffa3",
    "idempotent_replay": false,
    "requested_at": "2026-10-05T02:15:41.166635+00:00",
    "completed_at": "2026-10-05T02:15:41.169045+00:00",
    "target": {
      "id": "1000",
      "region": "fsn1"
    }
  }
}
```

The old submission and its records stay in your tenant. You can still [verify](/quickstarts/verify-a-signature) its signature or read its [audit trail](/api/audit).

## Update a server in place

A server that joined Tarsana can also take a new release without being replaced. [`request-update`](/api/request-update) sends it the release your release channel carries; the server installs it the next time it restarts, and goes back to the release it ran before if the new one does not start healthy. Follow it with [`update-status`](/api/update-status) until `update.state` is `confirmed`, `reverted` or `failed`.

## Servers you manage yourself

If you install Tarsana images on servers you created yourself, [`provision`](/api/provision) installs a newly built image on one of them. You name the provider, the server and its addresses in a provisioning request; the image always comes from the submission's finished build.

## Next steps

- [Verify a signature yourself](/quickstarts/verify-a-signature) for the new image.
- [Errors](/api/errors): what each refusal means and what to do.
