SealSkin

Getting Started

Install the server, connect a browser extension, the mobile app, or the web app, and launch your first isolated application.

SealSkin has two halves: a server that runs applications in Docker containers or Kubernetes pods and streams them, and a client (browser extension, mobile app, or the web app the server serves) that sends links, files, and downloads to it. This page takes you from nothing to a running session.

What you need

  • A Linux host with Docker and a user in the docker group. Sessions are ordinary containers on that host, so give it the CPU, memory, and disk you would give the applications themselves.
  • A hostname and a trusted TLS certificate if you want to use Firefox, the mobile apps, or the web app. The extension in Chrome and other Chromium browsers also works with the self-signed certificate the container generates, at the cost of one extra step (see Certificates).
  • One TCP port reachable from wherever your clients are. The default is 8443, which carries both the encrypted API and the streamed sessions. Port 8000 is a plain-HTTP fallback for the API that only the Chrome self-signed workflow needs.
  • A stable name for the server. It takes the name of its container, which container_name: sealskin fixes; if you run it some other way, set hostname: sealskin or SEALSKIN_NODE_NAME so the identity its records are kept under does not change when the container is recreated.
  • Optionally, a GPU. NVIDIA needs the proprietary driver (580 or newer) with the nvidia-container-toolkit (v1.20.1 or higher) and nvidia-drm.modeset=1; Intel and AMD need nothing beyond /dev/dri. See Administration.

SealSkin can be exposed directly on the internet: its own Caddy instance terminates TLS and authenticates every request to a session. It also runs behind a reverse proxy that holds the certificate or signs users in.

Install the server

The linuxserver/docker-sealskin repository ships an interactive script that creates the directories, obtains a Let's Encrypt wildcard certificate through Duck DNS, writes a docker-compose.yml, and starts the stack. Create a Duck DNS subdomain and copy its token first, then:

mkdir sealskin && cd sealskin
bash <(curl -sSL https://raw.githubusercontent.com/linuxserver/docker-sealskin/refs/heads/master/install.sh)

The script asks for the user and group id to run as (defaults to yours), the storage, config, and certificate paths (defaults under the current directory), the port to expose, your Duck DNS domain and token, and an e-mail address for Let's Encrypt. It ends with the address of your server, for example https://sealskin.example.duckdns.org:8443.

Forward the chosen port on your router to the host. If you access the server from inside the same network, your router or a local DNS server has to resolve the Duck DNS name to the host's private address ("split DNS"), because Duck DNS points at your public address.

Running the script again in the same directory offers to renew the certificate and restarts the stack with the new one.

With docker compose

If you already have a certificate, or want to start with the self-signed one, this is the equivalent compose file:

---
services:
  sealskin:
    image: lscr.io/linuxserver/sealskin:latest
    container_name: sealskin
    hostname: sealskin   # or SEALSKIN_NODE_NAME, for anyone not keeping the container name
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - HOST_URL=sealskin.example.com   # optional
    volumes:
      - /path/to/sealskin/config:/config
      - /path/to/sealskin/storage:/storage
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 8443:8443
      - 8000:8000   # optional, HTTP fallback for Chrome with a self-signed certificate
    restart: unless-stopped

A few things about this file are not negotiable:

  • The container must be named sealskin. The server inspects its own container to learn the host paths behind /config and /storage, the externally mapped ports, and the Docker network, and it finds itself by that name (falling back to its hostname). The name is also the server's identity among its records; hostname: sealskin or SEALSKIN_NODE_NAME pins it for a container run under another name.
  • The Docker socket is required. Every session is a container the server starts through it, so the container's user must be allowed to use the socket. If launches fail with a permission error from Docker, make the socket group-writable on the host (sudo chmod g+rw /var/run/docker.sock).
  • PUID and PGID own everything. The server, its files, and every session container run as that user, so the /config and /storage directories on the host must be writable by it.
  • HOST_URL is written into the generated admin configuration file so it can be imported as-is. Without it you edit the file once by hand.

To use your own certificate, place the PEM files at config/ssl/proxy_cert.pem and config/ssl/proxy_key.pem before the first start (or replace the self-signed ones later and restart). Everything else the server needs it generates itself; Configuration lists the files.

Other ways to run it

On Kubernetes, one manifest runs the server in any namespace you administer and starts every session as a pod beside it; Kubernetes covers it.

The server is also published as a Python wheel on every release. It needs Python 3.11 or newer, Caddy on the PATH, access to a Docker daemon, and the same key and certificate files. That route is meant for developers and is described in Development.

First run

On the first start with an empty /config the container:

  1. generates a self-signed certificate for the proxy (ssl/proxy_cert.pem, ssl/proxy_key.pem) unless you supplied one,
  2. generates the server's RSA key (ssl/server_key.pem) that anchors the end-to-end encryption,
  3. finds no administrator and writes the key file of the built-in administrator admin, its private key and connection details, to /config/admin.json, for the browser extension and the mobile app.
  4. writes the root key, an application key of the same admin, for the web sign-in to /config/root_key: paste it into the web app to sign in as admin, then delete the file.

Both files open the one account: a desktop launched from the web app is in the extension, and a key made under Application Keys in the web app signs the companion apps in without the key file.

Copy admin.json somewhere safe and delete it from the server once you have imported it into a client. It is the only copy of that private key; the server keeps just the public half. If HOST_URL was not set, open the file and replace the literal string HOST_URL with your server's hostname or IP.

Watch the log with docker logs -f sealskin. The server also warns there when the proxy certificate is within 14 days of expiring.

Install a client

ClientWhereNotes
Chromium browsersChrome Web StoreFull feature set: context menus, download interception, streamed downloads. Works with self-signed certificates.
FirefoxFirefox Add-onsContext menus and uploads. Requires a trusted certificate.
iOSApp StoreLauncher, files, and admin dashboard; sessions open in Safari. Requires a trusted certificate.
AndroidGoogle PlaySame as iOS; sessions open in a Custom Tab. Requires a trusted certificate.
Any browserhttps://<server>:8443/The web app: launcher, sessions, files, and dashboard, with a bookmarklet, web+sealskin: links, and a search engine in place of the context menu; installed from a Chromium browser it also receives shared links and files. Requires a trusted certificate. See Usage.

The zips, APK, and IPA on the releases page are the same builds for sideloading; Development covers loading an unpacked extension.

The extension and the app are thin shells. Everything you see after connecting, from the launcher to the admin dashboard, is served by your server, so the clients rarely need updating. The web app is those same pages with a small host around them.

Connect

Open the extension (toolbar icon or its options page), the app, or https://<server>:8443/. With no server configured you land on the connection page, which offers two ways in.

With an application key, the companion of the web app. Sign in to the web app, open Settings, and under Application Keys make a key for this device; it is shown once, with the server's address after an @. Paste it on the connection page and click Connect: the app asks the server who the key is and opens the options page. The device has to trust the server's certificate. Application keys has the rules an administrator can set.

With a key file, under Use a key file instead:

  1. Under Quick Setup, upload admin.json or paste its contents and click Apply Configuration. The manual form underneath takes the same values individually: server address, API port, session port, username, your private key, and the server's public key. It can also generate a key pair for you, which is how a user asks an administrator for an account without ever sending a private key.
  2. Click Login & Test. The client performs the encrypted handshake, signs a token with your key, and calls the server; on success it saves the configuration and opens the dashboard.

The web app takes no configuration file. It signs in with the root key the first start wrote to /config/root_key, or through an identity provider once one is set up.

Certificates

With the self-signed certificate, Chrome first tries https:// on the session port, and when the browser refuses the certificate it falls back to plain http:// on the API port for the encrypted API traffic (which is why port 8000 is in the compose file). The served UI still has to load over HTTPS: if the dashboard shows Server unreachable, click Open server to visit https://<server>:8443/ in a normal tab, accept the certificate, then Retry. After that both the API and the sessions use HTTPS.

Firefox does not let extensions talk to servers with untrusted certificates, and the mobile WebViews reject them outright, so those clients need a certificate the device already trusts. So does the web app. The installer's Duck DNS certificate satisfies all of them.

Add users and applications

As the administrator, the dashboard has everything you need for a first session:

  1. App Store lists the applications published by the default catalogue (linuxserver/sealskin-apps). Pick one, review who may use it (all by default) and Save Installation. The image is pulled on first launch, or ahead of time from Installed Apps.
  2. Users creates accounts. Leave the public key blank to have the server generate a key pair; the resulting configuration file is shown once, for you to hand to the user, who imports it exactly as you imported admin.json. Each user's permissions (storage, sharing, GPU, hardening, limits) are set here or through Groups.

Administration describes every panel.

Launch something

Click the SealSkin toolbar icon. The launcher lists the applications you may use; pick one, choose a GPU and storage if offered, and click Launch. The server starts the container, waits until it answers, and opens the session in a new tab through the session proxy.

From here on the interesting entry points are the right-click menu (open a link, open a link as a file, send an image or video, search selected text), Send Next Download to SealSkin, and the file manager. Usage covers them all.