Installation
Terminay ships as a desktop app for macOS, Windows, and Linux, and as a standalone server you can run on a Linux host or in a container and reach from the desktop app. Every build is published on GitHub Releases.
Desktop App
Download the asset for your platform.
| Platform | Asset |
|---|---|
| macOS 12 or newer, Apple silicon | Terminay-Mac-<version>-Installer.dmg |
| Windows 10 22H2 or Windows 11, x64 | Terminay-Windows-<version>-Setup.exe |
| GNU/Linux x64 | Terminay-Linux-<version>.AppImage |
Each asset carries a .sha256 sidecar. Verify it from the directory you downloaded into before installing.
shasum -a 256 -c Terminay-Mac-<version>-Installer.dmg.sha256On macOS, open the DMG and drag Terminay to Applications. The build is signed and notarized, so it opens without a Gatekeeper override.
On Windows, run the installer. It installs for your user only, with no administrator prompt. The installer is not yet code-signed, so SmartScreen warns about an unknown publisher the first time: choose More info, then Run anyway. Terminay finds PowerShell, Command Prompt, Git Bash, and WSL distributions as shell profiles. Windows on ARM is not supported, and Terminay Server does not run standalone on Windows.
On Linux, make the AppImage executable and run it.
chmod +x Terminay-Linux-<version>.AppImage
./Terminay-Linux-<version>.AppImageDesktop Updates
The desktop app checks for a newer release when it starts and about once an hour after that. On macOS, Windows, and a Linux AppImage, it downloads and verifies the update in the background. When it is ready, a Restart to update button appears in the title bar, and a What's new dialog lists the release notes. An update you have not installed is applied the next time you quit.
Choose Help > Check for Updates… to check immediately. A dialog reports whether you are up to date, an update is downloading, or one is ready to install.
Settings > Updates > Update channel selects Stable, which follows tagged releases, or Beta, which follows every merge to main and may be unstable. Beta also picks up stable releases when they are newer. Switching channel never installs an older version.
A build that cannot replace itself, such as one built from source, reports that a newer release exists and links to its release page instead.
About Terminay shows the running version. It is in the Terminay menu on macOS and the Help menu on Windows and Linux. On Windows, the menu button in the title bar, or a bare Alt, opens the application menu.
Server On Linux
Terminay Server runs your terminals, files, and project state on another machine. Installing it is one command on the machine you want to reach.
sudo npx terminay daemon installThat resolves the newest release, verifies its checksum and Ed25519 signature against a key built into the installer, unpacks it, writes a systemd unit, and starts the service. The archive bundles its own pinned Node runtime, the native PTY addon, and the workspace UI it serves, so the host needs neither Node nor a compiler.
Supported hosts are GNU/Linux x64 and arm64 with systemd and a Debian 12-compatible userspace (glibc 2.36 or newer). There is no way to skip verification: the key built into the installer is the only thing that makes the download trustworthy.
A stock container image has no systemd, so the installer refuses to run there. In a container, use the official image described under Server In Docker instead. Containers And Network Paths explains which network routes a containerised server needs.
The installer asks two questions when it has a terminal. The first is whether to install a system-wide service or one owned by your login. The second is which account the server and its terminals run as. That choice decides whose files, keys, and agents a paired device can reach, and a dedicated terminay account is the default. Answer both up front with --system or --user and --run-as <user>, which is also required when there is no terminal.
You can install something other than the newest release:
sudo npx terminay daemon install v4.1.1 # a specific release
sudo npx terminay daemon install main # the rolling main channel
sudo npx terminay daemon install my-branch # built from source on the target
A branch or commit has no published archive, so the installer builds one on the machine after checking for git, python3, make, and a C++ compiler.
Connect The Desktop App To It
Ask the server for a pairing code and approve the device that scans it, in one terminal:
sudo npx terminay daemon qr-codeThat prints a scannable QR code and the pairing URLs, then waits. When a device asks to enrol, it shows you the device name and a match code and asks you to approve it. Approve only if the code matches the one on the device. Paste the URL into Add connection in Terminay Desktop if you would rather not scan.
The QR code is the hosted link, which opens in a phone or desktop browser as well as in Terminay Desktop. The direct link printed beneath it is for Terminay Desktop only; --mode direct draws that one instead. Run as root, the command reaches the server through sudo, so sudo must be installed.
The pairing secret lives in the URL fragment and is short-lived and single-use. A paired device reconnects afterwards from its saved profile alone, with no link involved. On a machine with no terminal to approve from, use the scripted commands instead:
npx terminay daemon approvals
npx terminay daemon approve <approval-id>
npx terminay daemon deny <approval-id>Reaching It From Elsewhere
By default the server exposes itself both through the hosted relay and directly, and derives its direct origin from the machine's primary address. That is right for a host with a routable address and wrong for one behind NAT or with a DNS name, so the derived value is printed at install and can be overridden.
sudo npx terminay daemon install \
--direct-origin https://box.example.com:8443 \
--expose hosted,direct \
--port 8443Direct mode is authenticated by the server's host key, not by TLS. The listener presents a certificate the server generates into its own data root; a client verifies the host key's signature over the transport transcript before any credential crosses, so that certificate is not something to distribute or pin. Browsers cannot accept a self-signed listener, so direct mode is for the desktop app. Keep a reverse proxy with a real certificate if you want browser access too.
Keeping It Running
npx terminay daemon status # unit state, version, channel, exposure
npx terminay daemon upgrade # follow the installed channel
npx terminay daemon start
npx terminay daemon stop
npx terminay daemon reset-identity # new host key; every device pairs again
npx terminay daemon uninstall # keeps the data root unless --purge upgrade follows whatever channel the machine is already on, so a host tracking main never silently moves onto the tag stream. It stages the new version beside the running one, switches an atomic current symlink, and puts the previous version back if the new one does not report ready. It refuses to move to an older version unless you pass --allow-downgrade.
You can also upgrade from any paired device. When a newer release is out on the server's channel, the connections control in the header shows a warning icon, and the connection menu offers Upgrade server…. The connection drops while the server restarts and comes back by itself, with terminals still running. If the new release does not report ready, the previous one starts again and the menu says so.
reset-identity gives the server a new host key and revokes every paired device, so it asks for confirmation first. uninstall removes the service and the installed versions but keeps the data root, which holds the host key and your paired device records. Add --purge to remove that too, after which every paired device will have to pair again.
For hosts the installer does not support, the archives are installable by hand. The standalone server runbook documents that procedure along with every configuration option, hosted exposure, backup, and restore.
Server In Docker
The official image, markwylde/terminay, runs the same server in the foreground with hosted and direct exposure on. It is published for Linux x64 and arm64, so it also runs under Docker Desktop and Podman on macOS and Windows. Start it, then ask it for a pairing code:
docker run -d --name terminay \
-v terminay-data:/var/lib/terminay \
-v terminay-home:/home/terminay \
markwylde/terminaydocker exec -it terminay terminay daemon qr-code
The second command prints the QR code and pairing links and waits. When a device opens one, it shows the device name and a match code and asks you to approve it, exactly as on a Linux host. daemon approvals, approve, deny, and status work the same way through docker exec.
The two volumes are what survives the container. /var/lib/terminay holds the host key and your paired devices, so a new container on the same volume is the same server and nobody pairs again. /home/terminay is where terminals start and where your projects, shell history, and agent logins live.
Which Ports To Publish
Terminay Desktop on the same machine or network needs nothing more: open the hosted link in Desktop. Add -p 8443:8443 if you also want the direct link.
Browsers and phones need the address they reach this machine at, the signaling port, and the UDP range the session travels over:
docker run -d --name terminay \
-v terminay-data:/var/lib/terminay \
-v terminay-home:/home/terminay \
-p 8443:8443 -p 51000-51015:51000-51015/udp \
-e TERMINAY_PUBLIC_HOST=192.168.1.20 \
markwylde/terminay
Use a literal address there, not a name. On macOS ipconfig getifaddr en0 prints it, and on Linux hostname -I lists the machine's addresses.
On a Linux host, host networking gives the server the machine's real interfaces, so it needs no address and no published port. This does not work on macOS or Windows, where --network host joins Docker's virtual machine instead of your computer.
docker run -d --name terminay --network host \
-v terminay-data:/var/lib/terminay \
-v terminay-home:/home/terminay \
markwylde/terminayInstalling Packages
The server and every terminal run as the unprivileged terminay user, not as root. That user has passwordless sudo, so a terminal can install what a project needs:
sudo apt update
sudo apt install -y build-essentialPackages installed this way live in the container and are gone when it is recreated. For tools you want every time, build your own image on top of the official one:
FROM markwylde/terminay
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*
USER terminay Anyone who pairs a device can become root inside the container. To withhold that, add --security-opt no-new-privileges to the run command. sudo then refuses, and the server runs exactly as before.
Upgrading
Upgrade server… in the connection menu, or docker exec terminay terminay daemon upgrade, upgrades in place: the new release is verified and staged on the data volume, and the server restarts on it without restarting the container, so terminals keep running. daemon install, start, stop, and uninstall refuse inside the image. To move to a newer image, including its operating-system packages, pull it and recreate the container on the same volumes:
docker pull markwylde/terminay
docker rm -f terminay
# then the same docker run line as before latest is the newest release, and beta follows every merge to main. Version tags such as 5.14.0 and 5.14 pin a release. The standalone server runbook covers every setting and what to check when a device does not connect.
Build From Source
If a packaged build is not available for your platform, build Terminay locally. The repository pins Node.js 24.15.0 and npm 12.0.2.
git clone https://github.com/markwylde/terminay.git
cd terminay
npm ci
npm run build
Packaged output is written under release/<version>/. Release builds sync the app version from the Git tag before packaging, so local source builds appear under release/0.0.0/.