# Dedicated CPU cores & reservations
URL: https://synquic.tech/docs/guides/cpu-reservation.md

Pin a project to specific CPU cores, reserve them so nothing else on the server uses them, and set CPU priority and a memory guarantee.

import { Step, Steps } from 'fumadocs-ui/components/steps';

[Resources](/docs/guides/sleep-mode-resources) decide how MUCH CPU and memory a project may use. CPU
placement decides WHERE it runs and how it is treated when the server is busy. It is for the cases where a
limit is not enough: a latency-sensitive API that must not share cores with a batch job, a database that
should keep its memory when the box fills up, or a noisy neighbour you want to push to the back of the queue.

Everything on this page is optional and off by default. A project with no placement runs exactly as before.

<Callout title="What you need first">
- A project deployed to one of your own servers (placement uses Docker on that server; cloud and cluster
  projects are sized differently).
- Permission to change the project's settings.
</Callout>

## The four settings

| Setting | What it does | Docker flag |
| --- | --- | --- |
| Dedicated cores | Runs the project's containers only on the cores you pick. | `--cpuset-cpus` |
| Reserve exclusively | Also moves every other Synq container on that server off those cores. | `--cpuset-cpus` on the others |
| CPU priority | Relative weight under contention: Low 512, Normal 1024, High 2048, or any value from 2 to 262144. It never limits an idle server. | `--cpu-shares` |
| Memory reservation | A soft guarantee. When the server runs short of memory, containers above their reservation are reclaimed first. | `--memory-reservation` |

## Pin a project to cores

<Steps>

<Step>

### Open the project's Resources section

Open the project and choose **Resources** in the project menu. Below **Machine Power** is the **CPU & memory
placement** card. Open it; Synq reads the server's core count from its Docker daemon (for example "prod-1
has 8 cores.") and, when it can, the topology from `lscpu`, so cores are grouped by socket and hyper-thread
siblings show their physical core.

</Step>

<Step>

### Pick the cores

Under **Dedicated cores**, click cores to select them; **Clear selection** starts over. The legend shows
**Selected**, **Reserved by another project** (disabled), **Used by another project** (you can still share
them) and **Free**. Hover a core to see who holds it.

</Step>

<Step>

### Optionally reserve them exclusively

Turn on **Reserve exclusively** to keep every other Synq-managed container (other projects and their
services) on the remaining cores. At least one core always stays unreserved, for Synq itself and your other
apps.

</Step>

<Step>

### Set priority and memory, if you want

**CPU priority** offers **Low**, **Normal**, **High** or **Custom** (a weight from 2 to 262144; normal is
1024). **Memory reservation** takes a number of MB; it can't be more than the memory limit set under
**Machine Power**.

</Step>

<Step>

### Save placement

Press **Save placement**. The change applies live: Synq runs `docker update` on every affected container,
so nothing restarts. The toast says **Placement applied to N running containers.** and names any that could
not be updated. **In effect now** shows the cores, CPU weight and memory reservation actually in force.
**Remove placement** clears everything.

</Step>

</Steps>

For a compose or monorepo project, the **Apply to** menu picks **Whole project** or a single service, and the
**Per service** list shows which services have their own placement (**Same as project** otherwise; press
**Edit** to change one). A service without its own placement uses the project's, field by field, so a service
that only sets a CPU priority still runs on the project's cores.

If the server can't be reached, a placement that only sets **CPU priority** or **Memory reservation** is
saved anyway and applied when containers are next created. Picking cores (with or without **Reserve
exclusively**) needs the server to be reachable, because Synq reads its core count first; otherwise the save
is refused with **The server's CPU count could not be read, so cores cannot be pinned right now.**

## How exclusivity is enforced

A reservation is enforced in two places, which always compute the same answer:

1. **Live, when you save.** Every Synq container on the server is updated to its effective cores: the
   reserving project gets its cores, everyone else gets all the others. Releasing a reservation gives every
   container every core again.
2. **At every container create.** A deploy, a rollback, a zero-downtime swap, a restart that recreates a
   container and a scale-out all read the current reservations and create the container on the right cores.
   Placement is read live, never from an old deployment snapshot, so rolling back to a release from before
   another project reserved cores still stays off them.

The server page shows the same picture: **Servers** → the server → **Overview** → **CPU cores** lists which project pins or
reserves which core and each container's current cpuset, with a **Re-apply** button that runs the live update
again.

### Rules Synq checks before saving

- The cores must exist on the server.
- Two projects (or services) cannot reserve overlapping cores exclusively.
- A reservation cannot take cores another project is pinned to; unpin it there first.
- At least one core must stay unreserved.
- A memory reservation cannot exceed the project's memory limit.

## What a reservation does not isolate

<Callout type="warn" title="Host processes and the control plane">
Synq's own containers (API, dashboard, edge, database and cache) are never restricted, so a reservation can
never starve the platform. Processes that run directly on the host, outside Docker (sshd, cron jobs, the
Docker daemon itself), can still be scheduled on reserved cores.

For hard isolation, keep the kernel scheduler off those cores as well: add `isolcpus=2-3` (your reserved
cores) to the kernel command line, for example in `GRUB_CMDLINE_LINUX` in `/etc/default/grub`, run
`update-grub` and reboot. Docker can still place pinned containers on isolated cores, and nothing else will
run there.
</Callout>

Build containers are short-lived and are not moved; they may briefly use reserved cores while a build runs.

## From the API

```bash
# Read the placement, the server's cores and who holds them
curl -H "Authorization: Bearer $SYNQ_TOKEN" \
  "$SYNQ_URL/api/projects/$PROJECT_ID/cpu-placement"

# Reserve cores 2 and 3 exclusively, with high CPU priority
curl -X PUT -H "Authorization: Bearer $SYNQ_TOKEN" -H "Content-Type: application/json" \
  -d '{"placement":{"cores":[2,3],"exclusive":true,"cpuShares":2048}}' \
  "$SYNQ_URL/api/projects/$PROJECT_ID/cpu-placement"

# Release everything
curl -X PUT -H "Authorization: Bearer $SYNQ_TOKEN" -H "Content-Type: application/json" \
  -d '{"placement":null}' \
  "$SYNQ_URL/api/projects/$PROJECT_ID/cpu-placement"
```

The response includes an `apply` report: whether the server was reached, how many containers were updated,
which failed, and which containers only pick up a removed memory reservation on their next deploy (Docker
cannot remove a reservation from a running container). `SYNQ_URL` is your panel address (for example
`https://app.example.com`).

## Related guides

<Cards>
  <Card title="Resources" href="/docs/guides/sleep-mode-resources" description="CPU and memory limits: how much a project may use." />
  <Card title="Restart policy & container settings" href="/docs/guides/container-settings" description="Other Docker options for the same containers." />
  <Card title="Custom servers" href="/docs/guides/custom-servers" description="Add the servers your projects run on." />
</Cards>
