Running My Own Small Production Platform
I wanted a place where my projects could live after the fun part of building them was over. Dino’s Essentials became that place: a small production platform on my own VPS, with Dockerized applications, real domains, HTTPS, persistent data, and a deployment daemon that keeps each service current.
Why I stopped at “just deploy it”
Putting one app on a server is straightforward. Keeping several of them alive is where the actual systems work begins.
The first tools in the platform were Dino’s Finance Essential, a receipt and expense tracker, and Dino Speed Reader, a local-first RSVP reader. LARPmaxxing followed later. They do not share the same runtime or storage needs, but they do share a production machine.
I wanted every project to have a repeatable answer to the same questions:
- How does traffic reach the correct container?
- Where does persistent data live?
- How do I deploy a new commit?
- How do I recover a service without disturbing the others?
- How do I add the next app without rebuilding the server by hand?
That changed the project from “a VPS with some containers” into a small platform I could keep extending.
One server, isolated applications
Each application owns its Docker Compose configuration and runs as an isolated group of services. A web app might have a frontend, API, database, and worker; a smaller tool might only need one Node process and a persistent volume.
A reverse proxy sits in front of the applications. DNS sends each subdomain to the VPS, and the proxy routes that request to the correct service while handling HTTPS. The app containers bind only where they need to, so exposing a new product does not mean opening a new public port.
Internet
↓
DNS + HTTPS
↓
Reverse proxy
├── finance service group
├── speed-reader service
└── LARPmaxxing service
Docker Compose is doing more than saving me from long commands. It is the contract for each application: images, ports, health checks, volumes, restart behavior, and configuration all live next to the code.
The deployment daemon
The repetitive part was deployment. I would push a change, connect to the server, navigate to the correct repository, pull, rebuild the right containers, and verify that the application came back.
So I automated that loop.
The VPS runs a daemon that checks the configured GitHub repositories for new upstream commits. When it detects one, it updates the local checkout and rebuilds that project’s Docker Compose services. Unchanged applications are left alone.
New commit on GitHub
↓
Deployment daemon detects the change
↓
Update the matching checkout
↓
Rebuild and restart that Compose project
↓
Service returns behind the same HTTPS route
The daemon is intentionally narrow. It is not trying to be a general-purpose cloud platform or a homemade Kubernetes. It solves the deployment loop I actually have: a known set of small applications on one machine.
What operating it taught me
Building the app and running the app are different engineering jobs. The production version has forced me to think about failure boundaries, secrets, data persistence, health checks, reverse-proxy configuration, and what happens when a process exits at 2 a.m.
It also changed how I structure new projects. A project is much easier to ship when its production assumptions are explicit from the beginning. I now tend to add a health endpoint, Dockerfile, Compose service, environment template, and persistent-volume plan before I consider an app finished.
The best part is the compounding effect. The first deployment required building the platform. Each new product after that mostly needs a container, a route, and an entry in the daemon’s configuration. I still own the operational work, but I no longer have to perform every step manually.
Tech used
Ubuntu Server, Docker, Docker Compose, GitHub, DNS, Caddy/reverse proxying, Node.js, Python, PostgreSQL, and Linux service management.