Why We’re Writing This
While we spent and reworked the Nixopus installer many times, this feels like the right moment to share our experience and the lessons we learned along the way.
When your product is self hostable and open source, installation becomes tricky. Why? Because it is not the usual flow of opening a website, logging in, and clicking around to explore features. With Nixopus, we wanted people to experience everything from the ground up by self hosting it on their own infrastructure.
The Early Days: Bash Scripts
We started small. The first version was a simple Bash script for self hosting. It worked well for a few days. But as we began introducing new features and more ideas around improving the initial installation and self hosting experience, the Bash codebase started growing rapidly.The number of lines kept increasing and complexity crept in faster than we expected
At some point, it no longer felt like we were working on a few small scripts. It felt like we were building and maintaining a full user experience layer.
Simple tasks, like finding duplicates in an array, suddenly felt heavy. Not because the problem itself was hard, but because we were solving it in Bash. We were not here to solve a “find duplicates in an array” problem. We were here to build a product.
What we really wanted was something Straightforward, Quick to adopt, Easy to move fast with the installer needed to support the product, not slow us down.
Moving to a Proper CLI
This was also the point where we realized that if we wanted to scale this, we needed a proper CLI.
- Easier for users to try Nixopus
- Room to introduce more commands in the future
With that in mind, we chose Python Typer https://typer.tiangolo.com/
Why Python Typer
It is built on top of Rich, which made a big difference for us.
- Showing progress bars during installation
- Pretty printing command output
- Clean logging
All of this was straightforward. The overall developer experience was simple, fast enough, more than anything the experience it gives to user..
Refactoring the Installer
We then took our large Bash script and refactored it into multiple smaller files, each broken down by domain:
- SSH key generation
- Preflight checks
- Docker service management
- Dependency installation
- Proxy initialization
Each responsibility lived in its own focused module.
How the installer looks like
This is how it eventually turned out:
$ nixopus [OPTIONS] COMMAND [ARGS]...
We added a few core commands like install update version uninstall
Other smaller commands were intentionally left unregistered to avoid adding unnecessary complexity for the end user.
Keeping the CLI Clean but Flexible
When needed, we can easily register those internal commands from a single file and they work as expected. For example SSH key generation, and setting up environment variables. These can be tedious when configuring a development environment. Instead of handling that manually, it becomes as simple as running:
nixopus ssh [ARGS]
This approach keeps the default CLI clean while still giving us full control to expose more commands when they are actually needed.
The Next Problem We Hit
We felt good about the changes for a few days, and the early success of that decision was clear. But soon, new ideas started piling up. Now users had clear commands like install, but a new problem showed up.
- The
nixopus installcommand had a large number of configurable options - Each option existed for a reason
- Expecting users to understand all of them upfront was unrealistic
Discoverability vs Overwhelm
The real question became this. How do we help users discover and understand these parameters in a way that does not overwhelm them?
Take ports as a simple example:
- Different services use different ports
- Many servers already have ports in use
- Users need the ability to customize them
- Users also need guidance on when and why to do so
Documentation Isn’t Always Enough
Even though we have everything documented in our CLI reference
it can still be hard.Sometimes even for me and I self host Nixopus multiple times a week, patience and user behavior matter a lot here.
- Most users do not want to read long lists of flags
- They want things to work
- They want guidance only when something actually needs attention
The Install Command Generator
So we came up with a Nixopus install command generator. The idea was simple:
- Help users understand which flags do what
- Guide them interactively
- Generate the install command before they actually run it
This lives directly in our documentation and allows users to customize their nixopus install command step by step, with context, instead of guessing or digging through flags.
Conclusion
It is always the case of improving from your past experiences, there is no handbook for anything, so take a right decision at right times.
If you’re building something and facing similar challenges, I hope this story helps you make the right decisions. If you’ve already gone through something like this, I’d love to hear your experience. Follow me for more detailed stories like this.





Top comments (0)