> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siderolabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run Omni On-Prem

> Deploy a self-hosted Omni instance on any internet-connected Linux host

This guide walks you through deploying Omni on a Linux host with internet access. By the end, you will have a fully functional Omni instance running with TLS, an OIDC identity provider, and either embedded or external etcd.

<Note>Omni is licensed under the [Business Source License](https://github.com/siderolabs/omni/blob/main/LICENSE) and requires a support contract for production use. Contact [Sidero sales](mailto:sales@siderolabs.com) for a production license.</Note>

## Prerequisites

You will need:

* A Linux host with at least **2 vCPUs and 4 GB RAM**
* A domain name or IP address for your Omni instance
* **Docker** installed on the host
* The following ports open and accessible:

| Port  | Protocol | Purpose          |
| ----- | -------- | ---------------- |
| 443   | TCP      | Omni UI and API  |
| 8090  | TCP      | SideroLink API   |
| 8091  | TCP      | Event sink       |
| 8100  | TCP      | Kubernetes proxy |
| 5556  | TCP      | Dex OIDC         |
| 50180 | UDP      | WireGuard        |

## Step 1: Install Docker

Omni and its supporting services run as Docker containers. Follow the [official Docker installation guide](https://docs.docker.com/engine/install/) for your Linux distribution.

After installing, add your user to the `docker` group so you can run Docker without `sudo`:

```bash theme={null}
sudo usermod -aG docker $USER
newgrp docker
```

## Step 2: Install cfssl

`cfssl` generates and signs the TLS certificates used by Omni and Dex.

```bash theme={null}
CFSSL_VERSION=$(curl -sI https://github.com/cloudflare/cfssl/releases/latest \
  | grep -i location | awk -F '/' '{print $NF}' | tr -d '\r')

curl -L -o cfssl \
  https://github.com/cloudflare/cfssl/releases/download/${CFSSL_VERSION}/cfssl_${CFSSL_VERSION#v}_linux_amd64
curl -L -o cfssljson \
  https://github.com/cloudflare/cfssl/releases/download/${CFSSL_VERSION}/cfssljson_${CFSSL_VERSION#v}_linux_amd64

chmod +x cfssl cfssljson
sudo mv cfssl cfssljson /usr/local/bin/
```

## Step 3: Set environment variables

Set variables that define your host's network addresses and the internal hostnames for Omni and Dex. They are referenced throughout the guide, so set them before proceeding.

```bash theme={null}
export HOST_PUBLIC_IP=$(curl -s https://ifconfig.me)
export HOST_PRIVATE_IP=$(hostname -I | awk '{print $1}')
export OMNI_ENDPOINT=omni.internal
export AUTH_ENDPOINT=auth.internal
export OMNI_USER_EMAIL="admin@omni.internal"

echo "Public IP:  $HOST_PUBLIC_IP"
echo "Private IP: $HOST_PRIVATE_IP"
```

<Note>If your host does not have a public IP (e.g. a local VM), set `HOST_PUBLIC_IP` to the IP your local machine uses to reach the host.</Note>

### 3.1: Add internal hostnames to `/etc/hosts`

Omni and Dex use `.internal` hostnames that are not registered in public DNS. This maps them to localhost so the host can resolve them.

```bash theme={null}
echo "127.0.0.1 ${OMNI_ENDPOINT} ${AUTH_ENDPOINT}" | sudo tee -a /etc/hosts
```

## Step 4: Generate TLS certificates

Omni and Dex communicate over TLS. In this step, you create a root CA and use it to sign a certificate covering both service endpoints. The CA certificate is also distributed to client machines in **Step 9** so their browsers trust the Omni UI.

<Note>If you already have a trusted internal CA, use it to sign the certificate in **Step 4.3** and skip **Steps 4.1 and 4.2**.</Note>

### 4.1: Create the root CA

Run this command to generate a self-signed root CA, the trust anchor for all certificates in this setup.

It produces `ca-key.pem` (private key), `ca.pem` (public cert), and `ca.csr` (signing request).

```bash theme={null}
cat <<EOF > ca-csr.json
{
  "CN": "Internal Root CA",
  "key": { "algo": "rsa", "size": 4096 },
  "names": [{ "C": "US", "O": "Internal Infrastructure", "OU": "Security" }]
}
EOF

cfssl gencert -initca ca-csr.json | cfssljson -bare ca
```

Add the CA to the host's system trust store so services running on this machine trust certificates signed by it:

```bash theme={null}
sudo cp ca.pem /usr/local/share/ca-certificates/ca.crt
sudo update-ca-certificates
```

### 4.2: Create the signing configuration

Create a signing configuration that tells `cfssl` how to sign certificates. The `web-server` profile is used for Omni and Dex. The `client` profile is used later for external etcd authentication in **Step 7**.

```bash theme={null}
cat <<EOF > ca-config.json
{
  "signing": {
    "default": { "expiry": "8760h" },
    "profiles": {
      "web-server": {
        "usages": ["signing", "key encipherment", "server auth"],
        "expiry": "8760h"
      },
      "client": {
        "usages": ["signing", "key encipherment", "client auth"],
        "expiry": "8760h"
      }
    }
  }
}
EOF
```

### 4.3: Generate the server certificate

Generate a single wildcard certificate covering both the Omni and Dex endpoints. Both IPs are included so TLS connections work regardless of whether clients connect by hostname or IP.

```bash theme={null}
cat <<EOF > wildcard-csr.json
{
  "CN": "Internal Wildcard",
  "hosts": [
    "${OMNI_ENDPOINT}",
    "${AUTH_ENDPOINT}",
    "127.0.0.1",
    "${HOST_PUBLIC_IP}",
    "${HOST_PRIVATE_IP}"
  ],
  "key": { "algo": "rsa", "size": 4096 }
}
EOF
```

<Tip>If you plan to use [workload proxy](./enable-workload-proxy), add a wildcard SAN for your proxy domain to the `hosts` array now to avoid regenerating the certificate later. The exact value depends on your domain layout. See [Choose your domain layout](./enable-workload-proxy#step-2-choose-your-domain-layout).</Tip>

Sign the certificate using the CA and configuration from the previous steps:

```bash theme={null}
cfssl gencert \
  -ca=ca.pem \
  -ca-key=ca-key.pem \
  -config=ca-config.json \
  -profile=web-server wildcard-csr.json | cfssljson -bare server
```

Bundle the server certificate with the CA so clients can verify the full chain:

```bash theme={null}
cat server.pem ca.pem > server-chain.pem
```

This produces `server-key.pem` (private key), `server.pem` (public cert), and `server-chain.pem` (cert bundled with CA). Make them readable by all service users by running:

```bash theme={null}
chmod 644 server*.pem
```

## Step 5: Generate the etcd encryption key

Omni encrypts all data written to etcd at rest using a GPG key. This generates the key and exports it to `omni.asc`, which Omni reads on startup.

<Warning>Do not set a passphrase on this key. Omni reads the key file on startup and cannot prompt for a passphrase interactively.</Warning>

```bash theme={null}
gpg --batch --passphrase '' \
  --quick-generate-key \
  "Omni (Used for etcd data encryption) omni@internal.local" \
  rsa4096 cert never

FINGERPRINT=$(gpg --with-colons --list-keys "omni@internal.local" \
  | awk -F: '$1 == "fpr" {print $10; exit}')

gpg --batch --passphrase '' \
  --quick-add-key ${FINGERPRINT} rsa4096 encr never

gpg --export-secret-key --armor omni@internal.local > omni.asc
```

## Step 6: Set up an identity provider

Omni requires an OIDC or SAML identity provider for user authentication. This guide uses [Dex](https://dexidp.io/), configured with a static user. Dex can also act as a proxy to upstream providers such as LDAP, SAML, GitHub, and Okta, see the [Dex connector documentation](https://dexidp.io/docs/connectors/) for details.

If you already have an existing SAML or OIDC provider and want to connect it directly to Omni, skip this step and see [Authentication and Authorization](../security-and-authentication/authentication-and-authorization).

### 6.1: Create a password hash for the admin user

You will be prompted to enter a password. This becomes your Omni login password. The hash is stored in the Dex configuration and never transmitted in plain text.

```bash theme={null}
export OMNI_USER_PASSWORD=$(docker run --rm httpd:2.4-alpine \
  htpasswd -BnC 15 admin | cut -d: -f2)
```

### 6.2: Write the Dex configuration

Create a `dex.yaml` configuration file, which configures Dex to serve OIDC over TLS on port 5556, registers Omni as an OAuth2 client, and creates a single static login user.

```bash theme={null}
cat <<EOF > dex.yaml
issuer: https://${AUTH_ENDPOINT}:5556

storage:
  type: memory

web:
  https: 0.0.0.0:5556
  tlsCert: /etc/dex/tls/server-chain.pem
  tlsKey: /etc/dex/tls/server-key.pem

enablePasswordDB: true

staticClients:
  - name: Omni
    id: omni
    secret: omni-dex-secret
    redirectURIs:
      - https://${OMNI_ENDPOINT}/oidc/consume

staticPasswords:
  - email: "${OMNI_USER_EMAIL}"
    username: "admin"
    preferredUsername: "admin"
    hash: "${OMNI_USER_PASSWORD}"
EOF
```

### 6.3: Run Dex

Start Dex as a container, mounting the configuration file and TLS certificates. The `:Z` flag on volume mounts ensures compatibility with SELinux if it is enabled on your host.

```bash theme={null}
docker run -d \
  --name dex \
  --restart=unless-stopped \
  -p 5556:5556 \
  -v $(pwd)/dex.yaml:/etc/dex/dex.yaml:ro,Z \
  -v $(pwd)/server-key.pem:/etc/dex/tls/server-key.pem:ro,Z \
  -v $(pwd)/server-chain.pem:/etc/dex/tls/server-chain.pem:ro,Z \
  ghcr.io/dexidp/dex:v2.41.1 \
    dex serve /etc/dex/dex.yaml
```

## Step 7: Run Omni

In this step you will start the Omni container and connect it to the services you set up in the previous steps: the TLS certificates from **Step 4**, the encryption key from **Step 5**, and the identity provider from **Step 6**.

### 7.1: Get the latest Omni version

Fetch the latest Omni release tag from GitHub and store it in `OMNI_VERSION`:

```bash theme={null}
export OMNI_VERSION=$(curl -sI https://github.com/siderolabs/omni/releases/latest \
  | grep -i location | awk -F '/' '{print $NF}' | tr -d '\r')

echo "Using Omni version: $OMNI_VERSION"
```

### 7.2: Create the SQLite storage directory

Omni uses SQLite alongside etcd for certain internal state.

This command creates the directory on the host that will be mounted into the container:

```bash theme={null}
mkdir -p $HOME/sqlite
```

### 7.3: Configure EULA acceptance

Self-hosted Omni requires the first user to accept the End User License Agreement (EULA) before the instance can be used. Until the EULA is accepted, all UI and CLI actions are blocked at the API level.

You can accept the EULA programmatically when starting Omni by using CLI flags or a configuration file, or you can complete acceptance through the Omni UI after **Step 9**. Choose the method that best fits your deployment workflow.

<Note>
  If your organization has a separate agreement with Sidero Labs, that agreement takes precedence and the EULA does not apply.
</Note>

<Tabs>
  <Tab title="CLI flags">
    You can pass your EULA details as CLI flags when starting your Omni instance in **Step 7.4**:

    ```bash theme={null}
    --eula-accept-name="John Doe"
    --eula-accept-email="john@doe.com"
    ```

    You can also store this information as environment variables and pass them to your Omni instance. Replace the placeholders with your own values:

    ```bash theme={null}
    export EULA_NAME="Your Name"
    export EULA_EMAIL="your@email.com"
    ```
  </Tab>

  <Tab title="Configuration file">
    If you prefer to configure EULA acceptance using the Omni configuration file, and you are passing the configuration using the `--config-path` flag, add the following to your configuration file instead. Replace the placeholders with your own values:

    ```yaml theme={null}
    eulaAccept:
      name: Your Name
      email: your@email.com
    ```

    See the [Omni configuration reference](../reference/omni-configuration) for the full list of supported options.
  </Tab>

  <Tab title="UI">
    If you prefer to accept the EULA through the browser, skip this step and continue to **Step 7.4** without the EULA flags.

    When you open the Omni UI in **Step 9.4**, Omni redirects you to `/eula`, where you must complete EULA acceptance before you can log in.
  </Tab>
</Tabs>

### 7.4: Start Omni

Choose the etcd mode that matches your setup. Embedded etcd is suitable for a single Omni instance. External etcd is required for high availability or if you need to manage the datastore independently.

The commands below use CLI flags to start Omni. If you prefer to use a configuration file instead, see [Omni configuration Examples](../reference/omni-configuration).

<Tabs>
  <Tab title="Embedded etcd (default)">
    ```bash theme={null}
    docker run -d \
      --name omni \
      --net=host \
      --cap-add=NET_ADMIN \
      --device /dev/net/tun:/dev/net/tun \
      --restart=unless-stopped \
      -v $(pwd)/ca.pem:/etc/ssl/certs/ca-certificates.crt:ro,Z \
      -v $(pwd)/server-key.pem:/server-key.pem:ro,Z \
      -v $(pwd)/server-chain.pem:/server-chain.pem:ro,Z \
      -v $(pwd)/omni.asc:/omni.asc:ro,Z \
      -v $HOME/sqlite:/_out/sqlite:rw,Z \
      ghcr.io/siderolabs/omni:${OMNI_VERSION} \
        --name=omni \
        --cert=/server-chain.pem \
        --key=/server-key.pem \
        --machine-api-cert=/server-chain.pem \
        --machine-api-key=/server-key.pem \
        --machine-api-bind-addr=0.0.0.0:8090 \
        --private-key-source=file:///omni.asc \
        --event-sink-port=8091 \
        --bind-addr=0.0.0.0:443 \
        --k8s-proxy-bind-addr=0.0.0.0:8100 \
        --advertised-api-url=https://${OMNI_ENDPOINT}/ \
        --siderolink-api-advertised-url=https://${OMNI_ENDPOINT}:8090/ \
        --siderolink-wireguard-advertised-addr=${HOST_PUBLIC_IP}:50180 \
        --advertised-kubernetes-proxy-url=https://${OMNI_ENDPOINT}:8100/ \
        --auth-auth0-enabled=false \
        --auth-oidc-enabled=true \
        --auth-oidc-provider-url=https://${AUTH_ENDPOINT}:5556 \
        --auth-oidc-client-id=omni \
        --auth-oidc-client-secret=omni-dex-secret \
        --auth-oidc-scopes=openid \
        --auth-oidc-scopes=profile \
        --auth-oidc-scopes=email \
        --sqlite-storage-path=/_out/sqlite/omni.db \
        --initial-users=${OMNI_USER_EMAIL} \
        --eula-accept-name="${EULA_NAME}" \
        --eula-accept-email="${EULA_EMAIL}"
    ```
  </Tab>

  <Tab title="External etcd">
    Use this if you have a completed external etcd cluster. See [Run an etcd Cluster On-Prem](./run-an-etcd-cluster) to set one up first.

    Set the private IPs of your etcd nodes.

    ```bash theme={null}
    export ETCD1_IP=<etcd-node-1-private-ip>
    export ETCD2_IP=<etcd-node-2-private-ip>
    export ETCD3_IP=<etcd-node-3-private-ip>
    ```

    Start Omni with the external etcd flags. The `--etcd-embedded=false` flag disables the internal etcd instance. The `--etcd-endpoints` flag lists all three nodes so Omni can fail over automatically if one goes down:

    ```bash theme={null}
    docker run -d \
      --name omni \
      --net=host \
      --cap-add=NET_ADMIN \
      --device /dev/net/tun:/dev/net/tun \
      --restart=unless-stopped \
      -v $(pwd)/ca.pem:/etc/ssl/certs/ca-certificates.crt:ro,Z \
      -v $(pwd)/server-key.pem:/server-key.pem:ro,Z \
      -v $(pwd)/server-chain.pem:/server-chain.pem:ro,Z \
      -v $(pwd)/omni.asc:/omni.asc:ro,Z \
      -v $HOME/etcd-certs/ca.pem:/etcd/ca.crt:ro,Z \
      -v $HOME/etcd-certs/client.pem:/etcd/client.crt:ro,Z \
      -v $HOME/etcd-certs/client-key.pem:/etcd/client.key:ro,Z \
      -v $HOME/sqlite:/_out/sqlite:rw,Z \
      ghcr.io/siderolabs/omni:${OMNI_VERSION} \
        --name=omni \
        --cert=/server-chain.pem \
        --key=/server-key.pem \
        --machine-api-cert=/server-chain.pem \
        --machine-api-key=/server-key.pem \
        --machine-api-bind-addr=0.0.0.0:8090 \
        --private-key-source=file:///omni.asc \
        --event-sink-port=8091 \
        --bind-addr=0.0.0.0:443 \
        --k8s-proxy-bind-addr=0.0.0.0:8100 \
        --advertised-api-url=https://${OMNI_ENDPOINT}/ \
        --siderolink-api-advertised-url=https://${OMNI_ENDPOINT}:8090/ \
        --siderolink-wireguard-advertised-addr=${HOST_PUBLIC_IP}:50180 \
        --advertised-kubernetes-proxy-url=https://${OMNI_ENDPOINT}:8100/ \
        --etcd-embedded=false \
        --etcd-endpoints=https://${ETCD1_IP}:2379,https://${ETCD2_IP}:2379,https://${ETCD3_IP}:2379 \
        --auth-auth0-enabled=false \
        --auth-oidc-enabled=true \
        --auth-oidc-provider-url=https://${AUTH_ENDPOINT}:5556 \
        --auth-oidc-client-id=omni \
        --auth-oidc-client-secret=omni-dex-secret \
        --auth-oidc-scopes=openid \
        --auth-oidc-scopes=profile \
        --auth-oidc-scopes=email \
        --sqlite-storage-path=/_out/sqlite/omni.db \
        --initial-users=${OMNI_USER_EMAIL} \
        --eula-accept-name="${EULA_NAME}" \
        --eula-accept-email="${EULA_EMAIL}"
    ```
  </Tab>
</Tabs>

## Step 8: Verify services are running

Check that both Omni and Dex containers started successfully:

```bash theme={null}
docker ps --format "table {{.Names}}\t{{.Status}}"
```

**Expected output:**

```
NAMES    STATUS
omni     Up X minutes
dex      Up X minutes
```

Confirm Omni is serving on port 443:

```bash theme={null}
curl -k https://127.0.0.1:443
```

This should return the Omni HTML page.

## Step 9: Access the Omni UI

Omni is running on your host, but your local machine does not yet trust the CA certificate or know how to resolve the internal hostnames. The steps below set that up.

### 9.1: Copy the CA certificate to your local machine

Copy `ca.pem` from the host to your local machine. You will install it as a trusted root in the next step.

<Tabs>
  <Tab title="AWS">
    Replace `<HOST_PUBLIC_IP>` with your instance's public IP and `~/path/to/your-key.pem` with your SSH key path.

    ```bash theme={null}
    scp -i ~/path/to/your-key.pem ubuntu@<HOST_PUBLIC_IP>:~/ca.pem ~/Downloads/omni-ca.pem
    ```
  </Tab>

  <Tab title="GCP">
    Replace `<instance-name>` and `<your-zone>` with your GCP instance name and zone.

    ```bash theme={null}
    gcloud compute scp <instance-name>:~/ca.pem ~/Downloads/omni-ca.pem --zone=<your-zone>
    ```
  </Tab>

  <Tab title="DigitalOcean / Bare metal / Local VM">
    Replace `<HOST_PUBLIC_IP>` with your host's IP.

    ```bash theme={null}
    scp ubuntu@<HOST_PUBLIC_IP>:~/ca.pem ~/Downloads/omni-ca.pem
    ```
  </Tab>
</Tabs>

### 9.2: Trust the CA certificate

Add the CA to your local machine's system trust store. This tells your browser and OS to trust TLS certificates signed by this CA, including the Omni and Dex endpoints.

<Tabs>
  <Tab title="Linux (RHEL)">
    On Red Hat and Fedora based distros you can copy the `ca.pem` file into the `/etc/pki/ca-trust/source/anchors/` folder and then run the following command to generate the trusted root store:

    ```bash theme={null}
    sudo cp ca.pem /etc/pki/ca-trust/source/anchors/
    sudo update-ca-trust
    ```
  </Tab>

  <Tab title="Linux (Ubuntu)">
    On Ubuntu and Debian based Linux distros you can copy the `ca.pem` file into the `/usr/local/share/ca-certificates/` directory and rename it to `ca.crt`

    ```bash theme={null}
    sudo cp ca.pem /usr/local/share/ca-certificates/ca.crt
    sudo update-ca-certificates
    ```
  </Tab>

  <Tab title="macOS">
    For macOS you should open the *Keychain Access* application and drag the `ca.pem` file into the window to install it.
  </Tab>

  <Tab title="Windows">
    On Windows you should rename the certificate extension from `.pem` to `.crt`. You can then double click on the file and select *Install Certificate* -> *Local Machine*. You then need to select "Place all certificates in the following store" and select the *Trusted Root Certification Authorities*.

    If you're using Windows Subsystem for Linux (WSL) you should follow the Linux guide for installing the certificate.
  </Tab>
</Tabs>

### 9.3: Add internal hostnames to your local hosts file

Map the Omni and Dex hostnames to your host's public IP so your local machine can resolve them. Replace `<HOST_PUBLIC_IP>` with your host's public IP.

<Tabs>
  <Tab title="macOS / Linux">
    ```bash theme={null}
    sudo sh -c 'echo "<HOST_PUBLIC_IP>  omni.internal auth.internal" >> /etc/hosts'
    ```
  </Tab>

  <Tab title="Windows">
    Add the following line to `C:\Windows\System32\drivers\etc\hosts`:

    ```
    <HOST_PUBLIC_IP>  omni.internal auth.internal
    ```
  </Tab>
</Tabs>

### 9.5: Log into Omni

Open `https://omni.internal` in your browser and sign in with the email and password you set in **Step 6.2**.
