portman
transforms your local project URLs from http://localhost:8080 and http://localhost:3000 into pretty URLs like https://app.localhost and https://project.localhost.
portman
has three components:
- The CLI lets you register projects and assign unique autogenerated ports to each one.
- The shell integration automatically sets the PORT environment variable when you
cd
into the project's directory. - The caddy integration automatically generates a Caddyfile that caddy will use to reverse-proxy your localhost:* urls to https://*.localhost URLs.
# Install portman CLI
brew install canac/tap/portman
# Start caddy
brew services start caddy
# Install shell integration (pick one the right one for your shell)
# Note that fish shell integration is automatically installed if you installed portman via Homebrew
echo "eval $(portman init bash)" >> ~/.bashrc
echo "portman init fish | source" >> ~/.config/fish/config.fish
echo "eval $(portman init zsh)" >> ~/.zshrc
# Create a new project and autogenerate a unique port for it
cd /projects/app
portman create
# Check that the shell integration automatically set the $PORT
echo "Port is $PORT"
# Run the project's dev server how you normally would
npm run dev
# Open the app in the browser
open "https://app.localhost"
In addition assigning unique, autogenerated ports to projects, portman can also link running servers to a specific port and dynamically change which project links to that port while those servers are running.
Suppose that you have a work project that needs to be run at http://localhost:3000 because of OAuth configuration that is outside of your control. Further suppose that you have three git worktrees for that project. That gives you an isolated development space for the feature you're working on, a bug you're fixing, and a co-worker's code you're reviewing locally. Without portman, to switch from running the feature worktree to the bugfix worktree you have to stop the feature worktree server listening on port 3000 and start the bugfix worktree server. To run your co-worker's code, you have to then stop the bugfix worktree server and start the code review worktree server. Stopping and restarting servers like this is tedious, especially when frequently switching between projects. portman provides a way to dynamically change which project port 3000 is linked to without needing to stop and restart servers.
First, create a project for one of the worktrees, run portman link
with the port you want to access the project on, and start the server.
# In terminal tab 1...
cd /projects/worktree-feature
portman create
portman link 3000
npm run dev
Running portman link 3000
not only links the active project to port 3000, it also records the active project's origin
git URL and will automatically link future projects with the same origin
git URL to port 3000. Now you can create the rest of the projects and start the servers. Each one will use its unique, autogenerated port so they can all start simultaneously without conflict with each other.
# In terminal tab 2...
cd /projects/worktree-bugfix
# Create the project and link it to port 3000
portman create
npm run dev
# In terminal tab 3...
cd /projects/worktree-review
# Create the project and link it to port 3000
portman create
npm run dev
The last project to be linked to a port takes precedence, so http://localhost:3000 will be routed to the worktree-review
server. portman achieves this by setting up a reverse-proxy that sends traffic from http://localhost:3000 to the port that the project is linked to. The port that localhost:3000 is linked to can be changed without stopping any servers by running portman link
in the project's directory.
# Link http://localhost:3000 to the worktree-bugfix project
cd /projects/worktree-bugfix
portman link
# Sometime later...
# Link http://localhost:3000 to the worktree-review project
cd /projects/worktree-review
portman link
Projects can only be linked to one port at a time, so adding a new linked port removes the previous linked port.
portman link 3000 worktree-bugfix
# worktree-bug is linked to port 3001 and nothing is linked to port 3000
portman link 3001 worktree-bugfix
portman provides a simple web server for graphically viewing all of your projects and some basic information about them. It is available at https://localhost.
When you create a project, portman remembers the current working directory and associates it with the project. Later when you cd
to that directory again, portman activates the project by setting the $PORT
environment variable to the project's port. Note that the shell integration must be enabled for portman to be able to detect changes to the current directory. During activation portman also sets $PORTMAN_PROJECT
to the name of the active project and sets $PORTMAN_LINKED_PORT
to the port linked to the active project if there is one.
To create a project without tying it to a specific directory, use the --no-activate
flag. The project will not be linked to the current directory and therefore cannot be automatically activated. You must also manually provide a name for the project.
portman create service --no-activate
echo "Port for service is $(portman get service)"
portman can usually infer a reasonable name for a project when it is omitted from from create
. The default project is based on the directory, and portman attempts to normalize it to a valid subdomain by converting it to lowercase, converting all characters other than a-z, 0-9, and dash (-) to dashes, stripping leading and trailing dashes, combining adjacent dashes into a single dash, and truncating it to 63 characters.
cd /projects/app
# Project name defaults to "app"
portman create
Projects that don't auto activate aren't associated with a directory. As a result, the project name cannot be inferred and must be provided manually.
# Project name is explicitly set to "app"
portman create app --no-activate
portman has a few configuration parameters that can be tweaked. Run portman config show
to locate the default config file location. Run portman config edit
to open the configuration file with $EDITOR
. You might want to copy the contents of the default_config.toml
file as a starting point and then make your desired changes. The config file location can also be changed by setting the PORTMAN_CONFIG
environment variable.
PORTMAN_CONFIG=~/portman.toml portman config show
The config file is in TOML format. This is the default config:
ranges = [[3000, 3999]]
reserved = []
ranges
is an array of two-element [start, end]
arrays representing the allowed port ranges. The first element is the beginning of the port range, inclusive, and the second element is the end of the port range, inclusive. For example, [[3000, 3999], [8000, 8099]]
would assign ports from 3000-3999 and 8000-8099.
Defaults to [[3000, 3999]]
if omitted.
reserved
is an array of ports that are reserved and will not be assigned to any project. For example, if you want to assign ports between 3000 and 3999, but port 3277 is used by a something on your machine, set reserved
to [3277]
to prevent portman from assigning port 3277 to a project.
Defaults to []
if omitted.
Chromium-based browsers automatically resolve the localhost
tld to 127.0.0.1. To use other browsers or other tools, you may need to configure your DNS to resolve *.localhost to 127.0.0.1. I use NextDNS for ad blocking, and it's trivial to add a rewrite in NextDNS for *.localhost domains.
To show the active project's port in your Starship prompt, add this to your starship.toml
:
[custom.port]
command = 'if test -n "$PORTMAN_LINKED_PORT"; then echo "$PORT -> $PORTMAN_LINKED_PORT"; else echo "$PORT"; fi'
when = 'test -n "$PORT"'
format = ':[$output]($style) '
shell = ['bash', '--noprofile', '--norc']
Prints CLI usage information.
Prints portman version.
Prints the shell configuration command to enable the shell integration for the specified shell. You can add it to your shell config as documented in installation. If you installed portman via Homebrew, fish shell is integration is automatically installed and you don't need to modify your fish shell config file.
Creates a new project and assigns it a unique, autogenerated port. If project-name
is not provided, a default is calculated based on the current directory. project-name
is required if --no-activate
is present. If --no-activate
is present, the project is not associated with a directory and will never be activated by the shell integration. See project names for more details about default project names. By default, the project is linked to the port associated with its git repo if any, unless --no-link
is provided. If --overwrite
is present and the project already exists, it is updated instead of failing.
Prints a project's port. project-name
defaults to the active project. If --extended
is present, the project's name, directory, and linked port are also printed in addition to the port.
Deletes a project. project-name
defaults to the active project. Its autogenerated port may be assigned to another project in the future.
Deletes all projects whose directories don't exist anymore.
Lists each project in alphabetical order with its ports, directory, and linked port.
Links a project to the specified port. port
defaults to the port associated with the active project's git repo. project-name
defaults to the active project. By default, when portman link
is given a port but no project name, it looks up the origin
remote url of the active project's git repo and records the port that that repo is linked to, unless --no-save
is provided. portman link
uses this information when it is not given a port or a project name.
Unlinks the port from the project it was linked to.
Deletes the repo and its associated port.
Lists all repos and their associated ports.
Prints a valid Caddyfile that reverse-proxies all projects' ports to https://*.localhost URLs where the subdomain is the project name.
Regenerates the Caddyfile and reloads the caddy config. portman updates the Caddyfile and reloads caddy whenever it makes changes, so this command should only be necessary if something else outside of portman's control is manipulating the Caddyfile or caddy config.
Prints the configuration that is currently being used.
Opens the configuration file using the command in the $EDITOR
environment variable.