RSS Amplifier

DaKheera47 · Apr 5, 2026

How I Host a Separate JobOps Instance for Every User

0
Sign in to vote or save

This page did not load. You can still read it on the original site — the toolbar below keeps your place in the directory.

JobOps is a self-hosted job search tool. 2,500+ stars on GitHub, runs in Docker, and until recently the only way to get it was to set it up yourself. The problem: a large number of people who starred

JobOps is a self-hosted job search tool. 2,500+ stars on GitHub, runs in Docker, and until recently the only way to get it was to set it up yourself.

The problem: a large number of people who starred the repo told me they would like an always online version, without the headache of setting up a VPS themselves. So I built a hosted version. Here's how the infrastructure works.


The wrong idea first

My first instinct was path-prefix routing. jobops.app/shaheer-sarfaraz/ per user. Looks clean, one domain, seems simple enough.

Doesn't work. JobOps frontend is Vite -- asset paths get baked in at build time. If the app isn't built with base: '/shaheer-sarfaraz' in vite.config.ts, the JS and CSS try to load from / and everything explodes. You'd need a separate build per user, which obviously doesn't scale.

The sane approach is subdomains. shaheer-sarfaraz.jobops.app. Each instance runs at / like normal, and a reverse proxy handles routing the right subdomain to the right container. Way cleaner, and it means I don't have to touch the app itself at all.


The stack

  • Hetzner VPS: cheap, reliable, European, the obvious choice if you're not already using it

  • Cloudflare: wildcard DNS (*.jobops.app pointing at the box)

  • Traefik v3: reverse proxy, automatic TLS

  • Docker: one container per user, isolated data directory

  • GitHub Container Registry: hosts the JobOps image

On Hetzner specifically: I picked it because it's significantly cheaper than AWS or DigitalOcean for the same specs, and I've never had a reason to leave.


The interesting bit: Traefik

This is the part I didn't know about before building this, and it's genuinely clever.

Traefik watches the Docker socket directly. When a new container starts, Traefik notices it immediately. If that container has the right labels, basically just "here's my hostname, here's my port", Traefik automatically starts routing traffic to it and provisions a TLS cert via Let's Encrypt. No config file edits. No reloads. No downtime.

Before I found this out I was planning to use Caddy with a manually edited config file per user. That would have been fine for five users and a nightmare for fifty. The Docker label discovery is what makes this actually workable as a hosted product rather than a hobby project I'm manually babysitting.

The setup is: one shared Docker network, Traefik on that network watching for containers, every user container joins that same network with labels that tell Traefik what to do with it. That's the entire routing layer.

One thing that tripped me up: Traefik needs to actually join the shared network in its own compose config, not just reference it. Miss that and Traefik ends up isolated, routing to nothing, with no useful error. Obvious in hindsight.


Per-user data isolation

Every user gets their own data directory mounted into their container. No shared database, no bleed between users. Each instance is completely self-contained, so there's no privacy risk.


The onboarding flow

Someone pays on Stripe, I get a notification, I run a provisioning script that spins up their container and sets up their data directory, and they get an email with their subdomain. The whole thing takes a few minutes on my end. It's manual for now, deliberately so, while I'm still onboarding small numbers and want to keep an eye on how it goes.

The infrastructure side of onboarding is genuinely just one command. The manual part is the human bit: making sure they know what to expect, answering first questions, making sure nothing weird happened with their instance. That part I want to keep hands-on for a while.


Does it scale?

At idle, each container uses roughly 80-120MB RAM. The box handles a decent number of users before I need to think about this. And if I do need to upspec, Hetzner makes that straightforward.

This is MVP infrastructure. It's not clever, it's not over-engineered, and it works. That's the goal for now.


If you want the hosted version without any of this, early access is open at try.jobops.app. Your own subdomain, cancel any time.

Read on dakheera47.hashnode.dev

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.