# ternssh: an SSH workspace that runs on Cloudflare Workers

> ternssh packages a browser terminal, SFTP and a draggable dashboard into a Cloudflare Worker, with a Docker image for local or self-hosted use. It is GPL-3.0-or-later, and the last push to the repository was on 2026-08-29.

**haradakashiwa/ternssh** — A web-based SSH solution that can be deployed on Cloudflare Workers.

- Repository: https://github.com/haradakashiwa/ternssh
- Website: https://ternssh.com
- Stars: 630 · Forks: 155
- Language: TypeScript
- License: NOASSERTION
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/haradakashiwa-ternssh

## What ternssh puts in a browser tab

The README describes ternssh as an "SSH workspace on Cloudflare" and lists four parts: a draggable dashboard, a terminal, SFTP, and status monitoring. That combination is the point. A plain web terminal gives you a shell and nothing else; ternssh treats the browser as the whole working surface, so sessions, file transfer and host status sit in one place instead of three tabs or three tools.

The target user is someone who already has Cloudflare in the stack. The repository ships a wrangler.jsonc, a wrangler.production.jsonc.example and a deploy script that calls wrangler deploy, so the intended path is a Worker deployment rather than a VPS install. The Docker route exists too, and it is the one the README puts first, which suggests the authors expect people to try it locally before pushing anything to the edge.

It is not a general-purpose SSH client. There is no mention of a native desktop binary, no mobile app, no local key agent integration in the README. If you want a terminal on your laptop, this is the wrong shape of tool.

## How the deployment is wired

The repository is a two-package monorepo. The root package.json defines a postinstall script that runs npm install --prefix web and npm install --prefix server, so the frontend and the Worker code are installed separately. Builds go through the web package: npm run build delegates to npm run build --prefix web, and a postbuild step runs scripts/generate-production-config.mjs.

That postbuild script is the interesting part of the architecture. It generates the production configuration, and the deploy script re-runs it with a --require flag before applying D1 migrations and calling wrangler deploy. In other words, the production config is derived, not hand-edited, and the deploy path refuses to continue if generation fails. The D1 database binding is named ternssh in the migration commands.

The Dockerfile mirrors this in two stages. A builder stage installs both packages with npm ci and runs the web build; a runner stage installs only server production dependencies, copies the built public assets from the builder, and pins wrangler@4.24.3 globally. The container exposes 8787 and declares a healthcheck that fetches /api/health and exits non-zero on a failed response. A named volume at /app/.wrangler holds the state, which is what docker-compose.yml mounts as ternssh-data.

## Installing ternssh with Docker

The README calls the prebuilt image the recommended route. The command below starts the container, maps port 8787, and mounts a named volume at /app/.wrangler so the Worker state survives a restart. After startup, the README says to open http://localhost:8787.

```bash
docker run -d \
  --name ternssh \
  -p 8787:8787 \
  -v ternssh-data:/app/.wrangler \
  --restart unless-stopped \
  ghcr.io/haradakashiwa/ternssh:latest
```

If you prefer Compose, the repository ships two files. docker-compose.ghcr.yml pulls the published image; docker-compose.yml builds from the local Dockerfile. Both read the port from a PORT variable and default it to 8787.

```bash
docker compose -f docker-compose.ghcr.yml up -d
```

To authenticate through Cloudflare Access, the README says to set the ACCESS_TEAM_DOMAIN and ACCESS_AUD environment variables. The package.json cloudflare.bindings block describes them: ACCESS_TEAM_DOMAIN is the Access team domain such as your-team.cloudflareaccess.com, and ACCESS_AUD is the Application Audience tag from your Access app. Together they enable JWT verification. The README does not document what happens when only one of the two is set.

## Deploying to Cloudflare Workers and applying migrations

For the edge path, the repository expects a wrangler.production.jsonc file that does not exist in the tree. The deploy:config script copies it from the example:

```bash
npm run deploy:config
```

After editing that file, the release script builds the web frontend and publishes. The deploy step generates the production config with --require, applies D1 migrations to the remote database, and deploys the Worker.

```bash
npm run release
```

For local development there is a separate migration target that applies migrations to the local D1 instance rather than the remote one:

```bash
npm run db:migrate:local
```

The split matters. db:migrate hits the remote database and db:migrate:local does not, and the README does not spell out the difference between them. Running the wrong one against a production database is the kind of mistake the script names make easy to avoid only if you read them carefully. The README also does not document rollback, so there is no stated way to undo a migration once applied.

## Where ternssh stops being the right tool

The dependency on Cloudflare is the first limit. The README's own framing is "SSH management tool that runs on Cloudflare Edge", and the deploy scripts are wrangler commands throughout. The Docker image is a local wrapper around the same Worker code, not a portable server build. If your organisation does not use Cloudflare, you are running a Worker runtime on a container purely to avoid the platform, which is more moving parts than a plain SSH gateway.

Authentication is the second limit. The only authentication mechanism named in the README is Cloudflare Access, configured through ACCESS_TEAM_DOMAIN and ACCESS_AUD. The README does not describe a built-in user database, password login or local account system. If you cannot use Cloudflare Access, the README gives you no documented alternative, and it is silent on what the app does when neither variable is set.

Third, there is no release history. No releases were retrieved for this repository. The last push was on 2026-08-29, which is recent, but a commit stream is not the same as versioned releases, and the deploy path has no documented way to pin or roll back a version. Teams that need a changelog to plan upgrades will not find one here.

## ternssh against a conventional SSH gateway

The obvious alternative is a self-hosted web SSH gateway such as a shell-in-a-box style server behind a reverse proxy. The difference is where the trust boundary sits. A conventional gateway terminates SSH on a host you own, and the browser talks to that host over HTTPS. ternssh runs the workspace inside a Cloudflare Worker, so the request path goes through Cloudflare's edge and the Cloudflare Access layer becomes the front door.

That trade is real in both directions. You inherit Cloudflare's network and Access integration, and you inherit Cloudflare's runtime constraints and account requirements. A conventional gateway does not care which CDN or identity provider you use, but you own the patching, the TLS certificates and the uptime.

The second alternative is simply using an SSH client directly. A native client keeps keys on the machine, supports agent forwarding and does not route interactive sessions through a Worker. ternssh's value is the browser surface and the dashboard, not the SSH protocol itself. If nobody on the team needs a browser-based session, the native client wins on every axis the README does not mention.

## Licence, maintenance and upgrade cost

The repository has a LICENSE file, and both the README badge and the package.json license field say GPL-3.0-or-later. The GitHub metadata reports the licence as NOASSERTION, which means the platform's classifier did not match the file to a known licence. That discrepancy is worth resolving by reading LICENSE directly if you plan to redistribute or embed the code. GPL-3.0-or-later carries copyleft obligations, and this article cannot tell you what those mean for your product; that is a question for your own legal review.

On maintenance, the last push was on 2026-08-29, which is recent, and the repository is not archived. That is the whole of what can be confirmed. There are no retrieved releases, so there is no version cadence to reason about.

Upgrade cost is dominated by the wrangler pin. The Dockerfile installs wrangler@4.24.3 globally and the root package.json devDependency is wrangler ^4.24.3. A caret range in the dev dependency and an exact pin in the image means local development and container builds can drift apart. The D1 migrations are applied by command, not automatically on startup in the files shown, so an upgrade that adds a migration needs a deliberate db:migrate run.

## Conclusion

Adopt ternssh if you already run Cloudflare Workers and want an SSH terminal and SFTP surface without standing up a separate bastion host, or if you want to try it locally through the prebuilt image on port 8787. Do not adopt it if your access model depends on Cloudflare Access and you cannot supply ACCESS_TEAM_DOMAIN and ACCESS_AUD, or if you need a release history to plan upgrades against, since no releases were retrieved. Before committing, verify the licence text in LICENSE against the GPL-3.0-or-later identifier in package.json, confirm the D1 binding and migration path in wrangler.production.jsonc.example, and check that port 8787 is the one your environment expects.

## FAQ

### What is ternssh?

ternssh is a web-based SSH management tool that runs on Cloudflare Edge, described in the README as an SSH workspace with a draggable dashboard, terminal, SFTP and status monitoring. It is written in TypeScript and licensed GPL-3.0-or-later according to the README badge and package.json.

### How do I install ternssh?

The README recommends the prebuilt Docker image, run with a port mapping to 8787 and a volume at /app/.wrangler, after which you open http://localhost:8787. There is also a Docker Compose file for the published image and a Cloudflare Workers deployment path documented on ternssh.com.

### Does ternssh support Cloudflare Access authentication?

Yes. The README states that setting the ACCESS_TEAM_DOMAIN and ACCESS_AUD environment variables enables Cloudflare Access authentication, and the package.json bindings block says the pair enables JWT verification. The README does not document any other authentication mechanism.

## Sources

- [haradakashiwa/ternssh on GitHub](https://github.com/haradakashiwa/ternssh)
- [Issues](https://github.com/haradakashiwa/ternssh/issues)
- [Project website](https://ternssh.com)
- [README](https://github.com/haradakashiwa/ternssh/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/haradakashiwa-ternssh
