> Source: Onevium official documentation
> Article: Remote gateway: Docker, HTTPS, and private management
> Original URL: https://onevium.com/docs/developers/remote-gateway
> Language: English
> Updated: 2026-09-09
> Applies to: 1.1.23+

---

# Remote gateway: Docker, HTTPS, and private management

Build from a matching package and configure persistent storage, HTTPS, SSH pairing, and restart/rollback checks.

## When a remote gateway is useful

Use a server-hosted gateway when you need a fixed HTTPS address or must accept business events while the computer is offline. The assistant still executes on its designated Onevium device. The gateway does not make an offline computer run; queued work has a deadline.

Interface screenshots use isolated example data; captions identify empty tokens and unsaved drafts. Follow the connection checks below to verify your deployment.

## Prerequisite: obtain a matching deployment package

Before continuing, have:

- The **complete deployment source package** matching the desktop version, including `server/gateway/Dockerfile`, `compose.example.yml`, and the protocol/workers files referenced by the Dockerfile.
- Working Docker Engine and Compose v2, server SSH access, a domain, and valid TLS certificates.
- A dedicated persistent directory and a server account authorized to manage it.
- A working Onevium installation and model connection.

If the package is unavailable, obtain it from the maintainer first. This guide does not invent a public image or require ordinary users to clone a private repository. The image name below is a **local tag built on your server**.

Run Docker commands from the **package root on the server**. Run the SSH forwarding command on the computer hosting Onevium.

**Use origins for gateway settings:** the gateway address, `ONEVIUM_GATEWAY_PUBLIC_URL`, and `GATEWAY_URL` in API examples contain only scheme, host, and optional port. If the interface shows `https://gateway.example.com/api/v1` as the API address, use `https://gateway.example.com` for those values, removing `/api/v1`. The private management address is likewise only `http://127.0.0.1:8430`, without a management path.

## Step 1: save remote mode and export the device public key

In Onevium, open Channels → External Access → External access settings → Connection:

1. Choose Remote gateway under Gateway location.
2. Enter the planned `https://gateway.example.com` gateway address without `/api/v1`.
3. Expand Advanced remote settings and choose Private connection via SSH. You can enter `http://127.0.0.1:8430` as the planned private address and leave the token empty initially.
4. Save first. Changing mode pauses external access; only then select Export device identity.
5. Upload `onevium-device-public.json` to the server over a verified SSH connection.

![Enter the remote gateway HTTPS address; the connection change takes effect after saving.](https://onevium.com/docs/external-access/3cc390708/en/16-remote-gateway.png "Enter the remote gateway HTTPS address; the connection change takes effect after saving.")

The file contains `owner_id`, `device_id`, and `public_key`, not a private key. Do not generate a substitute private key for the desktop executor. Export does not mean the device is paired, a listener is enabled, or a service is published.

## Step 2: build a local image

From the matching package root:

```bash
docker build -f server/gateway/Dockerfile \
  -t onevium-gateway:preview-c2409f75f .
```

The Dockerfile uses Node 24.15.0 and builds dependencies inside the image. Do not rebuild native dependencies in the active desktop installation. A successful build produces an image, not a running deployment.

Prepare a **new dedicated data directory**. Check UID/GID using the server account that will also own SSH management access:

```bash
id -u
id -g
```

This example uses `/srv/onevium-gateway`. Verify it is not another service's directory before creating it with the appropriate permissions:

```bash
sudo install -d -m 700 -o "$(id -u)" -g "$(id -g)" /srv/onevium-gateway
```

Do not reinitialize or change ownership of an existing deployment. SQLite storage needs local/block storage with working file locks, not a shared multi-host database volume.

## Step 3: configure Compose and environment variables

Create `gateway.env` in the package root. Replace UID/GID with the preceding output, and use the real domain and paths:

```dotenv
GATEWAY_UID=1000
GATEWAY_GID=1000
ONEVIUM_GATEWAY_IMAGE=onevium-gateway:preview-c2409f75f
ONEVIUM_GATEWAY_DATA_PATH=/srv/onevium-gateway
ONEVIUM_GATEWAY_PUBLIC_URL=https://gateway.example.com
ONEVIUM_GATEWAY_ENABLED=1
ONEVIUM_GATEWAY_ACCEPTING_REQUESTS=0
ONEVIUM_GATEWAY_CALLBACK_HOSTS=
```

| Variable                               | Purpose                                                                          |
| -------------------------------------- | -------------------------------------------------------------------------------- |
| `GATEWAY_UID/GID`                      | Match container file access with the SSH management account                      |
| `ONEVIUM_GATEWAY_DATA_PATH`            | Persistent host directory mounted at `/data`                                     |
| `ONEVIUM_GATEWAY_PUBLIC_URL`           | Public HTTPS origin and expected public Host                                     |
| `ONEVIUM_GATEWAY_ENABLED=1`            | Explicitly start the server listener                                             |
| `ONEVIUM_GATEWAY_ACCEPTING_REQUESTS=0` | Reject new work during setup while retaining management and existing-query paths |
| `ONEVIUM_GATEWAY_CALLBACK_HOSTS`       | Comma-separated exact callback hostnames; empty blocks all callbacks             |

The supplied Compose file maps container `0.0.0.0:8420` to **host `127.0.0.1:8420`**. Do not expose port 8420 directly to the public network.

The base Compose file does not forward the admission switch or callback allowlist. Create `gateway.override.yml`:

```yaml
services:
  gateway:
    image: "${ONEVIUM_GATEWAY_IMAGE:?Set the local image tag}"
    environment:
      ONEVIUM_GATEWAY_ENABLED: "${ONEVIUM_GATEWAY_ENABLED:-1}"
      ONEVIUM_GATEWAY_ACCEPTING_REQUESTS: "${ONEVIUM_GATEWAY_ACCEPTING_REQUESTS:-0}"
      ONEVIUM_GATEWAY_CALLBACK_HOSTS: "${ONEVIUM_GATEWAY_CALLBACK_HOSTS:-}"
```

Do not skip this: adding unreferenced values to `gateway.env` does not automatically pass them into a container. Validate the merged configuration:

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml config --quiet
```

## Step 4: initialize identity and start with admission paused

Replace the public-key mount with its real absolute server path:

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml run --rm --no-deps --pull never \
  -v /srv/onevium-device-public.json:/device-public.json:ro \
  gateway init --identity-file /device-public.json
```

Expect `Gateway identity initialized. No listener started.` Initialization writes identity once. If the file already exists, inspect the existing deployment instead of deleting configuration to retry. [Compose run](https://docs.docker.com/reference/cli/docker/compose/run/) runs a one-off initialization here without publishing the service ports.

Start the gateway:

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml up -d --no-build --pull never gateway
```

Check private management and logs:

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml exec -T gateway node dist/cli.cjs manage --path state
```

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml logs --tail 50 gateway
```

The expected checks are management-state JSON and a startup log containing `status: listening` and `public_management: false`. These are checks to perform, not observations already made on your deployment.

## Step 5: configure public HTTPS reverse proxying

This example assumes Nginx is installed and running on the same server as the gateway. Add this server block to your site configuration and replace the domain and valid certificate paths:

```nginx
server {
    listen 443 ssl;
    server_name gateway.example.com;
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;
    client_max_body_size 20m;

    location / {
        proxy_pass http://127.0.0.1:8420;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_buffering off;
    }
}
```

Check syntax first and reload only after it passes. If validation fails, fix the configuration before reloading:

```bash
sudo nginx -t
sudo nginx -s reload
```

Preserve the public Host, forward WebSocket upgrade headers, and disable buffering for SSE. The [official Nginx WebSocket guide](https://nginx.org/en/docs/http/websocket.html) explains explicit forwarding of upgrade headers.

This configuration is for the **remote Docker gateway**. Do not substitute the built-in gateway's loopback Host rule, and never proxy Onevium's original web/management port.

From an authorized client, check:

```bash
curl -sS -o /dev/null -w '%{http_code}\n' \
  https://gateway.example.com/api/v1/management/state
```

Expect 404. Management is not public. Direct browser-script business requests carrying Origin are also rejected; call from your backend.

## Step 6: establish private SSH management and pair

The management socket is `/data/control/gateway.sock` inside the container, corresponding to `/srv/onevium-gateway/control/gateway.sock` on the host. The directory is mode 700; socket and control token are mode 600.

Retrieve the control token in a trusted server terminal:

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml exec -T gateway node dist/cli.cjs management-token
```

The output is a sensitive **private management token**. Paste it only into Onevium's matching password field, not into URLs, source code, or log screenshots.

On the Onevium computer, open and retain this forwarding connection:

```bash
ssh -o ExitOnForwardFailure=yes -N \
  -L 127.0.0.1:8430:/srv/onevium-gateway/control/gateway.sock \
  gateway-user@YOUR_SERVER
```

Use a verified SSH host and key; do not disable host checking. The SSH user needs permission to access the host socket. The local endpoint explicitly binds loopback. The [OpenSSH manual](https://man.openbsd.org/ssh#L) documents local-port forwarding to a remote Unix socket.

Return to Onevium's advanced remote settings:

| Field                      | Example                       |
| -------------------------- | ----------------------------- |
| Gateway address            | `https://gateway.example.com` |
| Management connection      | Private connection via SSH    |
| Private management address | `http://127.0.0.1:8430`       |
| Private management token   | The retrieved control-token   |

![Private management detail: enter the address and token, save, then pair. The token is empty and the draft is unsaved in this example.](https://onevium.com/docs/external-access/3cc390708/en/17-private-management.png "Private management detail: enter the address and token, save, then pair. The token is empty and the draft is unsaved in this example.")

Save, then select Pair this device. Expect a paired confirmation. If it fails, inspect SSH and private management rather than switching to public `/management`. A public URL alone cannot manage a self-hosted gateway.

## Step 7: publish, allow admission, and make a real call

1. In remote mode, create or select an application and configure its assistant and project capabilities.
2. Run capability checks and publish the service after they pass. See [external access](https://onevium.com/docs/developers/external-access).
3. Change `ONEVIUM_GATEWAY_ACCEPTING_REQUESTS` to `1` in server `gateway.env`, then rerun the preceding `up -d --no-build --pull never gateway` command to apply the environment.
4. Select Enable external access on the Onevium application page and confirm connectivity.
5. Use the application's key and authenticated subject to request a test reply. Save the admission receipt and query the actual Run result.

**A 202, running container, or paired device alone is not a completed end-to-end check.** Follow the same `run_id` to a real terminal state and the correct backend result. See [conversations](https://onevium.com/docs/developers/conversations) for API/SSE examples and [authentication](https://onevium.com/docs/developers/authentication) for credentials.

For callbacks, add exact hostnames to server `ONEVIUM_GATEWAY_CALLBACK_HOSTS`, reapply Compose, then configure a callback. Only public HTTPS/443 destinations are accepted; empty lists, private networks, and localhost do not bypass restrictions. See [Webhooks and callbacks](https://onevium.com/docs/developers/webhooks).

## Verify restart recovery

Stop test submissions and let existing runs reach terminal states, or complete necessary reconciliation, before restarting:

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml restart gateway
```

Use `restart` for the same configuration. After changing environment or image, use this guide's `up` command to recreate the container. Mounted storage is retained; see the [official up reference](https://docs.docker.com/reference/cli/docker/compose/up/).

Check that:

- Private management remains readable and application/device identity is unchanged.
- The original App Key still reads old runs and conversations.
- Completed attachments still download with matching contents.
- Reconnection does not replay work that started but has an unknown outcome.

Unknown outcomes can remain in `reconciling`, `pending_sync`, or similar states until confirmed. An online server can accept and queue work within deadlines, but cannot make a sleeping computer execute it.

## Pause, back up, and roll back

1. Pause the business sender and inspect active runs; confirm outcomes or record unresolved items.
2. Set `ONEVIUM_GATEWAY_ACCEPTING_REQUESTS=0`, reapply Compose, and verify that new work receives 503 while old results remain readable.
3. Pause external access in Onevium. For a consistent backup, stop the gateway and copy the **entire persistent directory** to a new restricted backup location.

```bash
docker compose --env-file gateway.env -p onevium-gateway \
  -f server/gateway/compose.example.yml -f gateway.override.yml stop gateway
```

Include identity configuration, databases and companion files, private control credentials, and attachments. Do not copy only a live SQLite file, use `down -v`, or delete `/data` as a rollback procedure.

Before reverting an image, confirm compatibility with the data format. If unknown, remain paused and obtain a compatible build or matching snapshot from the maintainer. An older snapshot may omit executions and callbacks that happened afterward; reconcile them rather than replaying business side effects. After recovery, keep admission at 0 until identity, old data, and connections are checked.

## Common failures

| Symptom                            | First checks                                                                   |
| ---------------------------------- | ------------------------------------------------------------------------------ |
| Build cannot find files            | Complete matching package and package-root build context                       |
| Init reports an existing file      | Existing initialization; do not overwrite owner/device identity                |
| Container exits after startup      | Enabled flag, server-config.json, directory permissions, logs                  |
| Correct address returns 403        | PUBLIC\_URL versus Host, proxy Host preservation, unexpected Origin            |
| 502 or dropped WSS                 | Host listener, TLS proxy, Upgrade headers, timeouts                            |
| Private management 401/unreachable | control-token, live SSH forwarding, loopback address, socket UID/GID           |
| Pair action disabled               | Saved settings, pending edits, configured private address and token            |
| 202 without completion             | Online Onevium device, published capabilities, approvals, run state            |
| Environment changes ignored        | Override forwarding and an up/recreate rather than restart alone               |
| Data missing after restart         | Changed DATA\_PATH or a different volume; do not hide it by initializing again |

## Next steps and acceptance boundary

Retain the image tag, deployment configuration, and backup location. Record real API, model execution, SSH, TLS, SSE, callback, and restart checks. Until then, describe the state as “deployment awaiting acceptance,” not “production live.”

Continue with [authentication](https://onevium.com/docs/developers/authentication), [business conversations](https://onevium.com/docs/developers/conversations), and [Webhooks and callbacks](https://onevium.com/docs/developers/webhooks).
