# Restart policy & container settings
URL: https://synquic.tech/docs/guides/container-settings.md

Choose what Docker does when your app stops, override the start command, and set Linux capabilities, sysctls, extra groups, an init process, GPU access and zero-downtime deploys.

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

Every app and service on your server runs in a Docker container. Synq picks sensible defaults for it, and
most apps never need anything else. This page covers the knobs for the cases that do: an app that should
**not** come back after it exits, a program that needs an extra Linux permission, a kernel setting such as
`net.core.somaxconn`, a model server that needs the GPU, or an app that must never run twice at the same time.

Everything here applies **the next time the container is created**, so save and then redeploy.

<Callout title="What you need first">
- A project that runs a container on one of your own servers. (A static site has no container, so it has
  none of these settings.)
- Permission to change the project's settings.
</Callout>

## Where the settings are

| Project shape | Where |
| --- | --- |
| A single app | Project → **Advanced** → expand the **Container** card. It has **Command**, **Cap Add**, **Cap Drop**, **Sysctls**, **Groups**, **Init**, **GPU**, **Zero downtime**, **Restart policy** and, for **On failure**, **Max retries**. Press **Save**. |
| A service in a compose or monorepo project | **Apps & Services** → the service → **Settings**. The **Runtime** section has **Command** and **Restart policy**; the **Container** section has **Cap Add**, **Cap Drop**, **Sysctls**, **Groups**, **Init** and **GPU**. Press **Save changes**. |

## Restart policy

The restart policy tells Docker what to do when the app's process exits or the server reboots.

| Option | What happens |
| --- | --- |
| **Always** | Restart whenever the process stops, and start again when Docker or the server restarts. The default for a single app. |
| **Unless stopped** | Like **Always**, but a container you stopped yourself stays stopped, even after a server reboot. The default for services. |
| **On failure** | Restart only after a crash (a non-zero exit code), up to **Max retries** times (1 to 100, default 5). A clean exit stays stopped. |
| **No** | Never restart. The container stays stopped until you start or redeploy it. |

Pick **On failure** or **No** for a one-shot task (a migration, an import script) that should run and then
stop, instead of looping forever. Keep **Always** or **Unless stopped** for anything that serves traffic.

<Callout title="A crash loop is still a crash loop" type="info">
A restart policy only restarts the container. If the app exits because of a bad variable or a missing
database, it keeps failing. Open the project's **Logs** to see why, and fix the cause.
</Callout>

In a `docker-compose.yml`, the `restart:` key of a service sets the same thing (`always`, `unless-stopped`,
`on-failure`, `no`).

## Command

**Command** replaces the start command Synq detected. It runs through `sh -c`, so you can chain steps:

```bash
npx prisma migrate deploy && node server.js
```

Leave it as detected to keep the default. For an image built by a builder such as Nixpacks, Railpack or
Buildpacks, the image's own start command is used.

## Linux capabilities (Cap Add, Cap Drop)

Docker starts a container with a reduced set of Linux capabilities. Some programs need one more, for example
a VPN needs `NET_ADMIN`, and hardening often means dropping everything the app doesn't use.

- **Cap Add**: extra capabilities, separated by commas or spaces, for example `NET_ADMIN, SYS_TIME`.
- **Cap Drop**: capabilities to remove from Docker's default set. `ALL` drops every one.

Write the names with or without the `CAP_` prefix, in any case. Synq checks each one against the list of
capabilities the Linux kernel defines and refuses unknown names. Up to 64 in each field.

A common hardening pattern for a web app that listens on a high port:

| Field | Value |
| --- | --- |
| **Cap Drop** | `ALL` |
| **Cap Add** | `NET_BIND_SERVICE` (only if it must listen on a port below 1024) |

## Sysctls

Kernel parameters for this container only, one `key=value` per line:

```text
net.core.somaxconn=1024
net.ipv4.tcp_keepalive_time=600
```

Only parameters the kernel keeps per container can be set this way: `net.*`, `fs.mqueue.*`,
`kernel.shm*`, `kernel.msg*` and `kernel.sem`. Anything else (for example `vm.max_map_count`, which
Elasticsearch asks for) applies to the whole server; Synq refuses it here, because Docker would fail to start
the container. Set those on the server itself, for example in `/etc/sysctl.d/`. Up to 64 entries.

## Groups

Supplementary groups for the user the container runs as: group names or numeric GIDs, separated by commas or
spaces, for example `999` to let the app read a socket owned by that group. Up to 32.

## Init

Turn on **Init** to run a small init process (tini) as PID 1. It forwards signals such as `SIGTERM` to your
app and cleans up zombie processes. Use it when your app doesn't handle signals itself (a shell script as
the entry point, for example) and takes the full stop timeout to shut down, or when it leaves defunct child
processes behind.

## GPU

**GPU** passes NVIDIA GPUs into the container, the same as `docker run --gpus`:

| Option | What the container gets |
| --- | --- |
| **Off** | No GPU. The default. |
| **All GPUs** | Every GPU on the server (`--gpus all`). |
| **Specific count** | That many GPUs, 1 to 64 (`--gpus 2`). Docker picks which ones. |

The server needs an NVIDIA driver and the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
After installing the toolkit, register it with Docker and restart Docker:

```bash
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
```

Before each build, Synq asks the server's Docker whether it has the `nvidia` runtime. If it doesn't, the
deploy stops right away with a message that names the server, instead of failing when the container is
created. `docker info` on the server lists the runtimes Docker knows (look for `nvidia` under **Runtimes**).

GPU access needs a container on one of your own servers. A deploy that asks for a GPU on Synq Cloud, a
Kubernetes cluster or the bare runtime is refused, because the GPU could not be passed through.

In a compose file, Synq reads the GPU reservation Docker documents:

```yaml
services:
  model:
    image: vllm/vllm-openai
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all        # or a number, such as 1
              capabilities: [gpu]
```

Picking GPUs by `device_ids` is not supported; use `count`.

## Zero downtime (single apps)

On by default. A deploy starts the new container **beside** the old one and switches traffic once the new
one is ready, then stops the old one.

- It needs the **Container IP** routing mode (**Advanced** → **Routing**). The default, **Auto**, uses a
  **Loopback port**; the new container can't bind the same port, so with it the old container always stops
  first, whatever this switch says.
- An app that publishes any host port (in its [Ports](/docs/guides/ports) editor) always stops the old
  container first, because a fixed port on the server can't be bound by two containers at once. The deploy log
  says so.
- Turn it **off** for apps that must never run twice at once: a bot that holds a single connection, a
  scheduler that would run jobs twice, an app that writes to a SQLite file.

## What isn't supported

Synq reads `cap_add`, `cap_drop`, `sysctls`, `group_add`, `init`, GPU reservations (`deploy.resources.reservations.devices`),
`restart`, `command` and `entrypoint` from a compose file. The following Docker options are **not** available, and a compose file that uses them
shows a warning when it's scanned:

| Option | What happens instead |
| --- | --- |
| `ulimits` | Docker's daemon defaults apply. |
| `privileged` | The container runs unprivileged. |
| `devices` | No host devices are passed through. For GPUs, use **GPU** above. |
| `security_opt` | The default seccomp and AppArmor profiles apply. |
| `shm_size` | `/dev/shm` stays at Docker's 64 MB default. |
| `user`, `working_dir` | The image's own user and working directory are used. |
| `hostname` | The host name is set to the service name. |
| `ipc`, `pids_limit`, `oom_score_adj`, `runtime` | Docker's defaults apply. |

<Callout title="Need higher open-file limits?" type="info">
Since per-container `ulimits` aren't available, raise the default for every container on the server in
`/etc/docker/daemon.json`, then restart Docker:

```json
{
  "default-ulimits": {
    "nofile": { "Name": "nofile", "Soft": 65536, "Hard": 65536 }
  }
}
```

Restarting Docker restarts every container on that server, so do it in a quiet moment. Containers get the
new limit when they are created, so redeploy the apps that need it.
</Callout>

## From the API

A single app's container settings are part of the project update:

```bash
curl -X PATCH -H "Authorization: Bearer $SYNQ_TOKEN" -H "Content-Type: application/json" \
  -d '{"runtimeOptions":{"restartPolicy":"on-failure","restartMaxRetries":3,"capDrop":["ALL"],"capAdd":["NET_BIND_SERVICE"],"sysctls":{"net.core.somaxconn":"1024"},"init":true}}' \
  "$SYNQ_URL/api/projects/$PROJECT_ID"
```

Keys are merged one by one; send `null` for a key to go back to its default. `gpus` takes `"all"` or a
number from 1 to 64, and `null` turns GPU access off. `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 for the same containers." />
  <Card title="File mounts" href="/docs/guides/file-mounts" description="Mount a config file or certificate read-only." />
  <Card title="Maintenance mode" href="/docs/guides/maintenance-mode" description="Take the app offline behind a 503 page without stopping it." />
</Cards>
