A monorepo containing multiple self-hosted web applications, each following a consistent architecture pattern with Docker containerization.
These started out decades ago as desktop/mobile apps for the most part.
The KitchenHub includes a shopping list system with store layout so that the items in the list sort based on the way you walk the store.. I hate backtracking.
VehicleHub is my vehicle maintenence tracking system.
RetirementHub is my attempt to streamline a rather clunky spreadsheet I used for tracking progress toward eventual retirement.
MailHub is perhaps a tad different being composed of multiple dockers. This is essentially my internal mail server which handles our email collecting from multiple accounts and sorting into local accounts where procmailrc organizes further. I wanted to migrate to Sieve but courier-imap doesn't have that so if I'm going to migrate to dovecot.. why not dockerize as well.
RPGHub is for tabletop RPG campaign and character tracking (when it lands in this repo).
This repository contains several independent web applications, each with its own backend API, frontend interface, and database schema. All services are designed to run on a single Docker host with centralized routing and TLS termination.
- KitchenHub - Shopping list management with store layout organization
- VehicleHub - Vehicle maintenance and service log tracking
- RetirementHub - Retirement budget and planning (household, income, expenses, savings limits, projections)
- MailHub - Internal mail server (multi-container: collection, delivery, IMAP; procmail/sieve-style organization). Different architecture from the other hubs.
KitchenHub, VehicleHub, and RetirementHub follow a consistent architecture:
- Backend: Node.js/Express REST API
- Frontend: React + Vite, served by nginx
- Database: PostgreSQL (shared instance)
- Deployment: Docker containers
To avoid conflicts, each service uses unique ports:
- KitchenHub: Backend
8080, Frontend8081 - VehicleHub: Backend
8090, Frontend8091 - RetirementHub: Backend
8100, Frontend8110 - Future services: Increment by 10 (8120/8130, 8140/8150, etc.)
This project is deployed on a local network with the following infrastructure:
-
Ubuntu Docker Host
- Runs all application containers
- Hosts PostgreSQL database
- Manages Docker networks and volumes
-
HAProxy
- Handles routing for all
*.yourdomain.comdomains - Performs TLS termination
- Routes requests to appropriate backend/frontend containers based on subdomain
- Handles routing for all
-
DNS Server / Router with ACME Support
- ACME: Creates and manages wildcard Let's Encrypt TLS certificate for
*.yourdomain.com - Certificate Distribution: Automatically copies the certificate to the HAProxy container
- DNS: Handles DNS resolution for all hosted services
- Tailscale Integration: Runs Tailscale client to support remote client routing
- ACME: Creates and manages wildcard Let's Encrypt TLS certificate for
-
Tailscale
- Provides secure remote access via VPN
- Split DNS Configuration:
- Local clients: Resolve service domains to local network addresses
- Remote clients: Resolve service domains to Tailscale addresses
- Enables seamless access from both local and remote locations
Internet/Tailscale
↓
HAProxy (TLS termination, *.yourdomain.com routing)
↓
Docker Containers (Backend/Frontend services)
↓
PostgreSQL Database
- Local Network: Services resolve to local IP addresses (e.g.,
192.168.x.x) - Tailscale Network: Services resolve to Tailscale IP addresses (e.g.,
100.x.x.x) - All services accessible via
{service}.yourdomain.comsubdomains
- Docker and Docker Compose installed on Ubuntu host
- PostgreSQL database (can be containerized or external)
- HAProxy configured for routing
- DNS server/router with ACME client configured for certificate management
- Tailscale configured with split DNS
Each service has its own directory with:
docker-compose.yml- Local development/deploymentportainer-stack.yml- Production deployment via Portainerenv.example- Environment variable templateREADME.md- Service-specific documentation
-
Navigate to service directory:
cd kitchenhub -
Create environment file:
cp env.example .env
Edit
.envwith your database connection details. -
Set up database schema:
psql -U postgres -d your_database -f database/schema.sql
-
Start services:
docker-compose up -d --build
-
Configure HAProxy: Add routing rules for the service subdomain (e.g.,
kitchenhub.yourdomain.com) pointing to the appropriate container ports. -
Configure DNS: Add DNS records in your DNS server for the service subdomain.
All backend services implement a health check endpoint at /api/health:
- Returns
200when ready - Returns
503when not ready - Used by Docker health checks and HAProxy monitoring
.
├── common/ # Shared code across services
│ ├── database/ # Database connection utilities
│ └── api/ # API client reference
├── kitchenhub/ # KitchenHub service
│ ├── backend/ # Node.js/Express API
│ ├── frontend/ # React + Vite application
│ ├── database/ # PostgreSQL schema
│ └── docker-compose.yml
├── vehiclehub/ # VehicleHub service
│ ├── backend/ # Node.js/Express API
│ ├── frontend/ # React + Vite application
│ ├── database/ # PostgreSQL schema
│ └── docker-compose.yml
├── retirementhub/ # RetirementHub service
│ ├── backend/ # Node.js/Express API
│ ├── frontend/ # React + Vite application
│ ├── database/ # PostgreSQL schema + migrations
│ └── docker-compose.yml
└── README.md # This file
See individual service README files for development setup instructions:
The common/ directory contains reusable code:
common/database/db-config.js- PostgreSQL connection pool (used by backends)common/api/api-client.js- API client reference (frontends inline this)
Backends import from common using relative paths:
const { createDbPool } = require('../../common/database/db-config');- All services run behind HAProxy with TLS termination
- TLS certificates managed automatically via ACME client (e.g., certbot, ACME on router/firewall)
- Remote access secured via Tailscale VPN
- Services communicate over internal Docker networks
- Database access restricted to backend containers
[Add your license here]