Open-source project
nilbuild/slim avatar
nilbuild/slim

Slim: HTTPS local domains and public tunnels from one Go binary

Give your localhost a local or public URL

2,119 stars139 forksGoNOASSERTION

At a glance

What is it?
Slim gives a localhost project a real https://myapp.test name by managing a local CA, hosts entries and port forwarding, and can put the same project on a public slim.show URL with a password, a TTL or your own domain. It mutates machine-wide state to do the first one, which is the trade you are signing.
Who is it for?
Adopt Slim for local development when https on a named domain matters, because cookie flags, secure contexts and third-party API callbacks all behave differently over plain http on localhost, and the path routing plus per-project .slim.yaml is better than juggling several tools.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two modes: a named local domain, or a public URL

Slim does two related jobs and they share an engine. The local mode gives a project on your machine a real HTTPS name, so myapp.test resolves to localhost:3000 and myapp.test/api routes to localhost:8080. The share mode puts the same local port on a public address.

The local mode is the more interesting one technically, because there is no DNS involved for .test domains. The project installs a certificate authority, adds hosts entries, and forwards the standard ports itself. The diagnostic output spells out exactly what state it maintains:

code
  ✓  CA certificate        valid, expires 2035-02-28
  ✓  CA trust              trusted by OS
  ✓  Port forwarding       active (80→10080, 443→10443)
  ✓  Hosts: myapp.test    present in /etc/hosts
  !  Daemon                not running
  ✓  Cert: myapp.test     valid, expires 2027-06-03

Two things in that sample deserve attention. The warning marker sits on the daemon line while everything else passes, which is a realistic state to hit on a fresh install: certificates and forwarding are prepared, and nothing is actually serving. And the port forwarding entries show that Slim wants the privileged ports, which is why it needs elevated rights and why two tools fighting over 80 and 443 will conflict.

Starting, stopping and naming local domains

The start command takes a name and a port. Omit the top-level domain and you get a .test name, which is the reserved TLD for local development and cannot leak to the public DNS root:

bash
slim start myapp --port 3000
slim stop myapp                  # stop one domain
slim stop                        # stop all domains

You can name any TLD you like, and the README warns against exactly one of them: .local is reserved for mDNS and causes slow DNS resolution on macOS and Linux. That is a specific piece of hard-won platform knowledge, and it is the kind of detail that separates a tool that has been used from one that has been assumed.

Routing beyond a single port is done with repeated --route flags, which is what makes one domain serve a frontend and several backends:

bash
slim start myapp --port 3000 --route /api=8080 --route /ws=9000

The websocket route matters more than it looks, since a live-reload connection and an API on the same origin removes a whole class of CORS problems during development.

One .slim.yaml per project instead of a wall of flags

When a project has more than one service, the per-command flags stop scaling, and that is what the config file is for. It lives at the project root and lists services, each with a domain, a port and optional routes:

yaml
services:
  - domain: myapp
    port: 3000
    routes:
      - path: /api
        port: 8080
  - domain: dashboard
    port: 5173
  - domain: app.loc
    port: 4000
log_mode: minimal  # full | minimal | off
cors: true         # enable CORS headers on proxied responses

Two global keys sit below the service list. log_mode switches per-domain access logging between full, minimal and off, and cors adds CORS headers to proxied responses, which is occasionally the difference between a working browser fetch and a silent failure that only appears on localhost.

The project-level lifecycle then mirrors the one-off commands:

bash
slim up                              # start all services
slim up --config /path/to/.slim.yaml # specify a config path
slim down                            # stop all project services

Committing this file is the intended workflow, since it makes a teammate's local environment a git checkout away rather than a wiki page of commands.

Public tunnels, and why some visitors get a warning page

Sharing runs slim share and needs slim login first. The options are richer than the usual random-subdomain tunnel:

bash
slim share --port 3000                              # random subdomain
slim share --port 3000 --subdomain demo             # https://demo.slim.show
slim share --port 3000 --password secret            # password protected
slim share --port 3000 --ttl 30m                    # auto-expires after 30 minutes
slim share --port 3000 --domain myapp.example.com   # custom domain

The default case, a random name like a1b2c3.slim.show, shows visitors a one-time warning page before their content loads. It tells them the site is being served from someone's computer and is not hosted or verified by the project, and it exists to keep the shared domain from being abused. That is a defensible design decision, and it is also a decision you should make knowingly: your demo opens on an interstitial rather than on your app.

The page is skipped for tunnels that give up anonymity, which the README lists as a password, a verified custom domain, or a reserved subdomain. Programmatic clients never see it at all: webhooks, curl, mobile SDKs, fetch and XHR calls, and CORS preflights go straight through. A browser-like client can bypass it with a header:

bash
curl -H "Slim-Skip-Browser-Warning: 1" https://a1b2c3.slim.show/

That asymmetry is the part to understand before you automate anything. If your test hits a random tunnel with curl, you will never see the warning your users see, which is exactly the kind of divergence that hides a problem until a demo.

Installing: a curl script, or build it yourself

The advertised install is a single line, and the requirements are Go 1.25 or later on macOS or Linux:

bash
curl -sL https://slim.sh/install.sh | sh

Building from source is documented with the upstream clone URL, which is worth pausing on if you arrived through a fork: the go module is declared as github.com/kamranahmedse/slim, and the README's build instructions clone that same path rather than the fork's own repository. Nothing breaks because of it, but it means the Makefile's version stamping, the install script and any upstream issues you read belong to the original module path.

The Makefile is small, and its two targets define the packaging contract:

bash
make build
make install

where build compiles with -s -w and injects a version string derived from git describe, falling back to dev when git describe has nothing to report, and also copies install.sh into docs/public so the hosted script and the repository stay identical. The install target moves the binary into /usr/local/bin, which on a typical machine means sudo, and that is consistent with a tool that also edits /etc/hosts and forwards privileged ports.

Updates and removal are one command each: slim update pulls the latest version, and slim uninstall removes the CA, the issued certificates, hosts entries, the port forwarding rules and the config.

Inside the binary: cobra, a TUI stack and a daemon

The dependency list describes the implementation accurately. spf13/cobra at 1.10.2 provides the command surface, which is why slim start, slim share, slim up, slim logs, slim doctor and slim uninstall all behave consistently. The charmbracelet stack of bubbletea, bubbles, lipgloss and lipgloss/v2 supplies the terminal interface, and the presence of go-osc52 alongside it points to inline terminal graphics for progress output.

Two more dependencies explain the architecture. github.com/coder/websocket at 1.8.14 is the transport used for the public tunnel, since a tunnel that terminates on an ingress and hands frames to your local port needs a websocket path back out. And github.com/sevlyar/go-daemon handles the background process that survives after you close the terminal, which is the same component the doctor output reports as not running.

Configuration parsing is gopkg.in/yaml.v3, and golang.org/x/net and x/sync sit alongside them for the proxying and concurrent connection handling. That is a short, conventional list: no database, no embedded browser, no external service in the local path.

Where Slim is the wrong tool

The main cost of the local mode is that it changes machine-wide state. It installs a CA into your trust store, edits /etc/hosts, and claims 80 and 443 through forwarding rules. On a work laptop with a device management profile, on a shared build box, or alongside another HTTPS-on-localhost tool, those are not actions you can undo casually, and slim uninstall is the documented way back rather than something you discover on a bad day.

The platform list is short: macOS and Linux. There is no Windows path in the requirements.

The sharing mode has a different boundary. A public tunnel exposes whatever is on that port to the internet, and the URL is short-lived by nature, so a tunnel left running is a production incident waiting for a demo to end. Use --ttl when you can.

Then there is the terms question and the shipping cadence. PolyForm Shield 1.0.0 is not an OSI-approved set of terms, which is a real constraint if your company has a policy about non-standard terms in a toolchain dependency, and the version tags are still in the 0.9 range, with the newest tag dated 2026-06-21 while the repository itself was pushed to on 2026-09-22. In other words, the newest tag is older than the newest commit, so what you get from a release is not necessarily what is in main.

Editorial conclusion

Adopt Slim for local development when https on a named domain matters, because cookie flags, secure contexts and third-party API callbacks all behave differently over plain http on localhost, and the path routing plus per-project .slim.yaml is better than juggling several tools. Do not adopt it on a machine where you cannot modify /etc/hosts and trust a private CA, and do not adopt it for production traffic, since the local mode works by rewriting system state and the share mode hands your port to someone else's ingress. Verify three things first. Run slim doctor before you trust anything, and read the port forwarding lines, because it wants 80 and 443 forwarded to 10080 and 10443 and the sample output shows a daemon that is not running, which is the state in which nothing will serve. Decide how you will share: a random subdomain shows visitors a warning interstitial and is meant for demos, while --password, --subdomain and --domain skip that page, and for a webhook you will be skipped anyway and never see it. And read the licence, because the project uses PolyForm Shield 1.0.0 rather than an OSI licence, which matters for a company evaluating a dependency. Start with slim start myapp --port 3000 and confirm the certificate in your browser before wiring anything else.

Frequently asked questions

How do I get a HTTPS domain for a local project with Slim?

Run slim start myapp --port 3000, which serves https://myapp.test on that port. Omitting the TLD gives you a .test name; you can pass any other TLD except .local, which is reserved for mDNS.

How do I share a local port on a public URL with Slim?

Run slim login first, then slim share --port 3000. Options include a named subdomain, --password for protection, --ttl 30m for automatic expiry, and --domain for a custom domain you have verified.

What is the browser warning page on a random slim.show subdomain?

It is a one-time interstitial shown on browser page loads to tell visitors the site comes from someone's computer. It is skipped for password-protected, verified custom-domain and reserved-subdomain tunnels, and never shown to programmatic clients.

Can Slim route different paths of one domain to different ports?

Yes. Use repeated --route flags such as --route /api=8080, or list paths under routes in the .slim.yaml file at the project root. Services, log_mode and cors are also defined in that file.

How do I uninstall Slim and clean up system changes?

Run slim uninstall, which removes the CA, issued certificates, hosts entries, the port forwarding rules and the config. Run slim doctor first to see what state it currently maintains.

Official sources

  1. Issues
  2. nilbuild/slim on GitHub
  3. Project website
  4. README
  5. Releases