Pracandy
← Newsroom

Features

Running Five Products on One VPS

What actually shares a machine well and what does not: one Node process behind host-based routing, a static site, a Postgres database, and scheduled jobs. Where the memory goes and when to stop.

7 September 2026

Running five distinct products on a single virtual private server is feasible, but only if you understand which components actually share resources and which remain isolated. The core constraint is not CPU speed, but memory allocation and process isolation. On a 12GB RAM instance, a single Node.js process serving multiple domains via host-based routing, a second independent Node process, a shared PostgreSQL database, and scheduled systemd timers can coexist without conflict, provided the static assets do not touch the database layer. The key is recognizing that static files served directly from disk consume negligible resources, while dynamic processes compete for memory pages.

Process Isolation and Routing

The architecture relies on two distinct Node.js processes. The primary Next.js process handles four domains: pracandy.com, flix.pracandy.com, walls.pracandy.com, and the admin interface. This single process uses host-based routing to distinguish between these sites, meaning they share the same memory heap and event loop. If one route becomes computationally heavy, it impacts the others. This is an acceptable trade-off for simplicity, as the total memory footprint of a single Next.js instance is significantly lower than running four separate ones.

In contrast, Instalaz runs as its own separate Node process on port 3050. This isolation is deliberate. Scheduling jobs often involve holding connections open or processing queues, which can spike memory usage unpredictably. By keeping Instalaz separate from the main Next.js process, a memory leak or spike in the scheduling tool does not freeze the film publication or the wallpaper app. Caddy proxies traffic to both processes, terminating TLS for all sites and obtaining certificates from Let's Encrypt automatically. This setup ensures that the dynamic parts of the stack are contained within their own process boundaries.

Static Assets and Disk I/O

GamingCap and Invoiceful are static builds. They are served directly from disk by Caddy. There is no server process running for these two products, and they do not connect to the database. This distinction is critical for resource management. Static files are read from the page cache in memory, which is fast and efficient, but they do not require the overhead of a Node.js runtime. Because they have no server process, they cannot leak memory or compete for CPU cycles in the way a dynamic application does. They simply exist as files on the 193GB disk. Caddy handles the delivery, making them effectively invisible to the system’s resource contention issues.

This separation means that the "heavy lifting" of the system is confined to the two Node processes and the database. The static sites act as passive endpoints. If the VPS disk fills up, the static sites will fail to load, but this is a storage issue, not a compute issue. Keeping static content separate from dynamic logic simplifies debugging; if a static site is down, the problem is almost certainly file permissions or disk space, not code logic.

Database and Scheduled Work

There is one PostgreSQL 16 database shared by the Next.js application and Instalaz. Sharing a single database instance reduces the overhead of managing multiple database servers, but it introduces a shared dependency. Both applications write to and read from the same data store. The memory usage of PostgreSQL is determined by its configuration, specifically shared_buffers and work_mem. On a 12GB machine, this must be tuned carefully to leave enough RAM for the two Node processes and the operating system. If the database grows too large for the available RAM, performance degrades as the system starts swapping, which slows down all connected services.

Scheduled work runs as systemd oneshot services with timers. This is preferable to cron for several reasons. Systemd provides better logging visibility and dependency management. If a scheduled job fails, systemd records the exit code and standard output, making diagnosis straightforward. These jobs do not run continuously; they start, execute a task, and exit. This prevents them from holding memory indefinitely. The timers ensure that tasks run at specific intervals without requiring a persistent daemon. This approach keeps the system clean and predictable, avoiding the "zombie process" issues that can arise from poorly managed cron jobs.