# Eight compose profiles, a localhost-only origin list, and a placeholder encryption key

> Hoppscotch is a TypeScript API client under the MIT license, shipped as a web app, a PWA, a desktop build and a CLI, and this repository holds all of it: a pnpm workspace, a Postgres-backed backend, and a compose file with eight profiles. Reading the configuration files rather than the feature list shows three defaults that break a real deployment. An origin allowlist containing only localhost addresses, an encryption key that ships as a placeholder string, and a proxy toggle that hides your address from the very API you are calling.

**hoppscotch/hoppscotch** — Open-Source API Development Ecosystem • https://hoppscotch.io • Offline, On-Prem & Cloud • Web, Desktop & CLI • Open-Source Alternative to Postman, Insomnia

- Repository: https://github.com/hoppscotch/hoppscotch
- Website: https://hoppscotch.io
- Stars: 80,510 · Forks: 6,126
- Language: TypeScript
- License: MIT
- Published: 2026-08-08 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/hoppscotch-hoppscotch

## Eight compose profiles, and two of them collide on ports

docker-compose.yml organises the deployment as Docker Compose profiles rather than as separate files, and the list at the top of that file is the whole configuration surface: default for the all-in-one service plus a database and auto-migration, default-no-db when you already have a Postgres, then backend, app, admin, database, just-backend for local development without the webapp, and deprecated, which the file itself marks as not recommended.

Each service carries its own profiles array. hoppscotch-backend belongs to backend, just-backend, app and admin, builds from prod.Dockerfile with target backend, reads ./.env through env_file, and is set to restart always. A comment beside it puts that backend on port 3170.

Starting a deployment comes down to picking one profile:

```bash
docker compose --profile default up
docker compose --profile default-no-db up
docker compose --profile backend up
docker compose --profile just-backend up
```

The failure mode is spelled out in the file as well: the default and default-no-db profiles should not be mixed with individual service profiles as they would conflict on ports. Nothing in the compose output names the port a given profile claimed, so a mixed invocation shows up as a bind error rather than as a line telling you which two profiles are fighting.

## WHITELISTED_ORIGINS only opens localhost, so a public host breaks sign-in

The backend talks to browsers through an allowlist, and the shipped value in .env.example is a single line:

WHITELISTED_ORIGINS=http://localhost:3170,http://localhost:3000,http://localhost:3100,app://localhost_3200,app://hoppscotch

Three of those are the localhost ports for the app, the backend and the development servers. The comments go on to explain that 3200 in app://localhost_3200 is the bundle server that provides the bundles and not where the app runs, because the app itself uses the app:// protocol with dynamic bundle names like app://{bundle-name}/.

What the default cannot do is name a real host. Serve the built app on a domain and the backend refuses the cross-origin calls, so sign-in and sync fail while the static interface still loads and looks healthy. The list needs editing before the first request rather than after the first failed login, and the symptom lands in the browser console rather than in the compose logs, so an instance that looks fine in the service list can be entirely unusable.

## DATA_ENCRYPTION_KEY ships as placeholder text, and rotating it strands stored secrets

Two values in .env.example need replacing before anything else. DATABASE_URL points at postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch, which carries the example password testpass. DATA_ENCRYPTION_KEY is labelled as the sensitive data encryption key while storing in database, 32 characters, and its shipped value is the literal string data encryption key with 32 char.

A placeholder that reaches production is not a weak key, it is a published one. Whatever the backend encrypts before writing to the database is then encrypted under text anyone can read in the repository, and nothing in the startup path fails to warn you.

The harder problem comes later. The key is read from the environment, not from the database, so changing it does not re-encrypt rows that are already stored. Tokens, secrets and environment values written under the old key stop decrypting, and the self-host admin dashboard has no way to show them afterwards. Pick the value before the first migration, because changing it later is not a re-run of a setup command.

## Proxy Mode hides your address from the API, which is what breaks IP allowlists

Proxy Mode is switched on from Settings, and the README gives it four jobs: hide your IP address, fix CORS problems, reach APIs served on non-HTTPS http:// endpoints, and use your own proxy URL. The official server is hosted by Hoppscotch, with the proxyscotch repository and a privacy policy linked from the README.

.env.example sets the destination and says what it is for. PROXY_APP_URL="https://proxy.hoppscotch.io" is optional, clients control whether requests are proxied through the in-app toggle, and the variable only decides which URL they default to. Removing it moves the decision to the admin dashboard, which is the sole server-side control in this arrangement.

The privacy gain and the breakage come from the same mechanism. Because the proxy is what hides your address, the API on the other end sees the proxy's address rather than yours, so any target that allowlists your office or your CI egress range starts rejecting calls once the toggle is on. TRUST_PROXY=false is the same trade-off read from the server, where the backend does not treat the client address as the left-most X-Forwarded-For entry unless told to, which is the setting any reverse-proxy deployment has to change.

## preinstall runs only-allow pnpm, so the workspace refuses npm

The root package.json is marked private, names the workspace hoppscotch-app, and globs ./packages/* as its workspaces. The package manager is pinned, with packageManager set to pnpm@10.34.5, and the preinstall script is npx only-allow pnpm. The install command is not a matter of preference, because anything other than pnpm aborts before dependencies resolve.

The scripts show what a build involves. dev runs pnpm -r do-dev, generate runs pnpm -r do-build-prod, and start serves the built output with http-server on port 3000 from packages/hoppscotch-selfhost-web/dist. lint, typecheck and test fan out the same recursive way.

The block worth reading before building an image is pnpm.overrides. It pins brace-expansion, js-yaml, minimatch, fast-uri, form-data, cross-spawn, execa, liquidjs, find-my-way, deepmerge-ts and @xmldom/xmldom to exact versions. The image you produce is the one those pins describe, and moving any of them is a deliberate edit to this file rather than something a lockfile refresh does on its own.

## The root manifest says 3.0.1 while the releases are tagged 2026.8.2

The version field in the root package.json reads 3.0.1, on a package marked private. The published tags follow a different scheme and a different clock: 2026.8.0 on 2026-08-28, 2026.8.1 on 2026-09-14 and 2026.8.2 on 2026-09-23, with the last push to the main branch on 2026-09-25.

So 3.0.1 is the workspace manifest's own number rather than an application release anyone deploys. Pinning a self-hosted instance to it pins the wrong artifact, and the two schemes do not line up, with semver in the manifest and a calendar-style year.month.patch in the tags.

The cadence is worth planning around too. Three tags landed in the 26 days to 2026-09-23, which makes an upgrade a routine event rather than an exception. The repository is not archived and the main branch moved five days before that release, so following the tags is a sound default. Reading them as 8.x minor bumps is not, because the tag prefix is a year and not a major version.

## Teams, workspaces and cloud sync sit behind a sign-in, and local storage is the fallback

Sign-in is offered through GitHub, Google, Microsoft, Email and SSO, and once you are in, five things synchronise across devices: workspaces, history, collections, environments and settings. Team features build on that, with unlimited teams, unlimited shared collections, unlimited team members, role-based access control, cloud sync and multiple devices. Workspaces sit alongside, keeping personal and team collection environments apart.

What the account buys is the collaboration layer, and the server offers no way around it. Role-based access control and shared collections exist only for a signed-in user, so an organisation wanting a shared, permissioned collection store on its own infrastructure has to stand up the backend, the admin dashboard and the database, because local session storage is the only store that needs no identity at all.

Collections do have a documented exit that avoids an account: export and import as a file or a GitHub gist. Neither is presented as a permissioned store, so anything that has to be reviewed or audited belongs on the self-hosted backend, and the export is a transport format.

## The feature list is browser-shaped, and the top level documents no CLI install

Every capability in the README is described from inside a browser tab, down to installing the app as a Progressive Web App with Service Workers for instant loading and offline support, listed alongside a Desktop PWA. The protocols are the usual set of WebSocket, Server-Sent Events, Socket.IO, MQTT and GraphQL, with authorization running from None and Basic through Bearer Token, OAuth 2.0 and OIDC Access Token/PKCE. Pre-request scripts run any JavaScript functions, and post-request tests check the status code as an integer, filter response headers and parse the response data.

The repository description, though, advertises Web, Desktop and CLI. Nothing at the top level is a CLI manifest or a desktop build script: the root scripts generate the web build and then serve it, and the workspace is a single ./packages/* glob whose members are not enumerated at the top level. For a team standardising on a command-line client there is therefore nothing here to install, and the packaging has to be confirmed before it goes into a pipeline.

## Conclusion

Hoppscotch fits a team that wants a self-hosted API client on its own Postgres and is willing to own the deployment, since the compose profiles, the MIT license and the pnpm workspace are all present. It does not fit a shop that needs a command-line client or a browser extension without first finding packaging this repository does not cover. Before the first deploy, replace DATA_ENCRYPTION_KEY with a real 32-character value and add your own host to WHITELISTED_ORIGINS, because those are the two defaults that fail quietly rather than loudly.

## FAQ

### Is Hoppscotch better than Postman?

The repository description positions Hoppscotch as an open-source alternative to Postman and Insomnia, and the license is MIT. What that buys in practice is self-hosting: docker-compose.yml ships eight profiles, including a default profile that runs the all-in-one service together with a PostgreSQL container, so the whole client can run on your own hardware. The README does not make a feature-by-feature comparison.

### What does Hoppscotch do?

It is an API client that sends HTTP requests and covers WebSocket, Server-Sent Events, Socket.IO, MQTT and GraphQL in the same interface. A request carries a standard method or a custom method you type in, pre-request scripts run JavaScript before the send, and post-request tests check the status code, filter response headers and parse the response data.

### Is Hoppscotch safe to use?

The code is MIT licensed, but two defaults decide how much traffic leaves your network. With Proxy Mode enabled, requests travel through the proxy server hosted by Hoppscotch, and .env.example points the default at https://proxy.hoppscotch.io. A self-hosted install also puts a 32-character DATA_ENCRYPTION_KEY in charge of everything stored encrypted, and the value shipped in .env.example is placeholder text.

### how to use hoppscotch for localhost

WHITELISTED_ORIGINS in .env.example already lists http://localhost:3170, http://localhost:3000 and http://localhost:3100, which are the app, backend and development server ports, so a default local setup needs no change there. The two things to fix first are DATA_ENCRYPTION_KEY and the browser mixed-content rule, since Proxy Mode is what lets a page on https:// call an http:// endpoint.

### how to install hoppscotch

For self-hosting, the repository ships docker-compose.yml with a default profile that runs the all-in-one service together with PostgreSQL and an auto-migration step. Building from source needs pnpm rather than npm: the root package.json sets packageManager to pnpm@10.34.5 and its preinstall script runs only-allow pnpm, which aborts any other installer.

## Sources

- [Official documentation](https://hoppscotch.io)
- [Official README](https://github.com/hoppscotch/hoppscotch#readme)
- [Project repository](https://github.com/hoppscotch/hoppscotch)
- [Release notes](https://github.com/hoppscotch/hoppscotch/releases)

---

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