Skip to content

Engineering3 min read

One VPS for the whole studio site: Next.js, Go, PostgreSQL and Dokploy

How onlyapps.org runs on a single server — a Go API, a job queue inside PostgreSQL, Next.js for the site and console, Dokploy for deploys — and four lessons from launch day.

The new onlyapps.org — the public site, the console our team uses to edit it, the API and every background job — runs on a single VPS. No managed databases, no Redis, no external CI. Here is how it is put together and what we learned while deploying it.

The pieces

PartTechnologyWhat it does
WebsiteNext.js 16Server-rendered pages in English, Serbian and Russian
ConsoleNext.js 16Editors, publishing, settings for the team
APIGo, chi, pgx, sqlcAll business logic, authentication, media
WorkerGo, RiverScheduled publishing, emails, IndexNow, cache refresh
DatabasePostgreSQL 18Data and the job queue
PlatformDokploy, Traefik, Let's EncryptBuilds, routing and TLS on our VPS

One database for data and jobs

Background jobs live in PostgreSQL through River, a Go job queue built on Postgres. The biggest win is transactions: when an editor publishes a post, the content change and the jobs it triggers — refresh the site cache, notify IndexNow, email subscribers — are written in one transaction. Either everything happens or nothing does, and there is no second system to keep alive.

Content as data, not HTML

Posts and documents are stored as structured JSON from the editor and rendered by the site component by component. No HTML is stored, so there is nothing to sanitize on the way out, and the same content renders consistently in every language.

Fast pages without a CDN

The site uses Next.js cache components. Pages are served from cache and invalidated by tags the moment something is published — the worker calls the site and only the affected pages are rebuilt. Media is stored on a Docker volume and served by the Go API on its own subdomain, with images converted into lighter WebP or JPEG variants. In our test runs Lighthouse mobile scores land in the low 90s for performance and 100 for SEO.

Deploying with Dokploy

The whole stack is one Docker Compose file with six services: website, console, API, worker, PostgreSQL and a backup container that runs pg_dump every night and keeps two weeks of dumps. Dokploy pulls the repository, builds the images on the server and routes the domains through Traefik with Let's Encrypt certificates. Database migrations run automatically when the API starts.

Four things we learned on launch day

  1. Unique service names on a shared network. On a server with many projects, Dokploy connects public services to one shared Docker network. Other projects there had their own api, web and postgres services, so a plain http://api:8080 could reach the wrong container. Internal addresses now use unique aliases such as onlyapps-api.
  2. Define domains in one place. Our compose file declared Traefik routers, and Dokploy generated its own for the same hosts. We moved all domains to Dokploy and kept only middlewares — security headers and the www redirect — in the compose file, each on its own service.
  3. Large uploads need a longer read timeout. Traefik v3 closes a request body after 60 seconds by default, which cut off video uploads on slower connections. Raising the entrypoint read timeout fixed it.
  4. Passwords inside URLs. A base64-generated database password can contain a slash, which breaks a postgres:// connection string. Hex passwords avoid the problem entirely.

Tip

Before the first deploy, check whether your secrets end up inside URLs, and generate them in hex if they do.

Why we keep it this simple

Everything is tested before it ships — every change goes through linting, unit and integration tests, a production build and end-to-end tests with accessibility checks. The result is a stack a small team can understand completely, move to another server in an evening, and run for the price of one VPS.

ShareXLinkedIn

ONLYAPPS team

Novi Sad, Serbia · onlyapps.org

Contact

We use analytics cookies only with your consent. Essential cookies keep the site working. Privacy policy