rollcat's website

Fix your port numbers for dev servers

Even if you don’t have a mild case of ADHD, you will often find yourself tasks switching and running different web development servers throughout the day. Regardless of the impact on your sanity, this also poses a port number of technical problems.

Problem

Following your chosen framework’s defaults, you may end up with port conflicts - e.g. Django uses port 8000 for each new project. This can prevent you from easily running dev servers side-by-side. Even when you run only a single server, your web browser will get confused with history, caches, etc.

There’s also a case for running these dev servers on a shared webhost and without containers - which is an entirely valid choice for simple, shared development environments.

Questioned solutions

  1. Assign a new port in sequence: 8001, 8002, etc. That solution doesn’t scale even for personal projects - you may sometimes forget which is the next free port, start using a different scheme by accident (8083, 8084…). Gets more confusing when you adopt different frameworks (5005, 5006), or even something without any convention. This is also bad for collaborative work - your co-conspirator may start a different project at a similar time you did, and boom the sequence is messed up.

  2. Keep some sort of a central database: a sticky note, a spreadsheet, a chat log with a pinned message, etc. Of course improving organisation and communication is always desirable - but in any case, such databases may drift away from reality, and maintaining them imposes additional overhead. The ultimate source of truth is whatever is hardcoded in your application.

My solution

Assign a port number deterministally - based on the project name. Your code remains the source of truth, the snippet can be trivially copy-pasted (and customized), and it will do all of the negotiations (including with your future self) for you.

It works something like this:

// Pick a unique project name. This is the hardest part.
projectName = "helloworld"
// The `hashStr` function must return an **unsigned** integer
port = hashStr(projectName)
// Modulo-then-add will wrap the integer to the 1024-65535 range.
port = 1024 + port % 64512

Simple, huh? Now this one does not completely prevent conflicts, the birthday problem will still haunt you. But that’s the beauty of copypasta. If you still do run into a conflict, you can tweak the code, e.g. decrease the modulo. It will still do its job when copy-pasted into a different project.

In any case, you should still honor a command-line flag and $PORT.

Bonus feature: this works for mapping other identifiers, e.g. UIDs on shared SSH boxes. Now running rsync -a does the correct thing.

Appendix: examples

Appendix (n.):

Blind-ended tube connected to the cecum, from which it develops embryologically.

Note that in these examples, the project name must consist of alphanumeric characters. I couldn’t come up a succint variant that doesn’t have this limitation.

Go net/http

projectName := "project"
port, _ := strconv.ParseInt(projectName, 36, 64)
port = 1024 + port%64512
addr := fmt.Sprintf("127.0.0.1:%d", port)

Python

I usually use the same method as in Go:

project_name = "project"
port = 1024 + int(project_name, 36) % 64512

With Flask, you can substitute app.import_name for project_name.

Django is a little special. It will honor the command line flag as well as the $DJANGO_PORT variable, but setting the default port programmatically is a little bit ugly. You can this in your ./manage.py:

from django.core.management.commands.runserver import Command as runserver
runserver.default_port = port

Word of warning: it may be a bad idea to use the builtin hash function in place of int(..., 36): the documentation does not define whether the output is stable between releases, or even differernt runs of the same program. If you want to take that risk, remember to use abs(hash(project_name))

Other languages / frameworks

If you’d like to contribute, drop me a line.