Decision-Making Breakdown
Deployment: Dokploy Black-Box vs Transparent Deployment
For a Hostinger VPS with Express, Next.js, and PostgreSQL:
Dokploy Black-Box
Typical behavior: A managed deployment workflow where internal build and deploy details are abstracted.
Pros:
- Simple push-to-deploy experience
- Handles build pipeline orchestration
- Reduces infrastructure setup effort
Cons:
- Harder debugging during failures
- Limited configuration flexibility
- Lower operational visibility
Good when: You need minimal DevOps overhead for a very small team or early MVP.
Transparent Deployment (non-black-box)
Examples:
- Docker
- Ansible scripts
- PM2 for Node services
- Manual NGINX and TLS configuration
- PostgreSQL as managed service or self-hosted service
Pros:
- Full control
- Easier debugging
- Infrastructure as code
- Better team knowledge sharing
Cons:
- More setup effort
- Requires DevOps skills
Best for this stack when prioritizing:
- Documentation quality
- Reproducible environments
- Team onboarding and handoff
- Reliability at scale
VPS Recommendation
Use containerization with simple CI/CD:
- Dockerize Express and Next.js
- Use
docker compose(orsystemdservices where required) - Put NGINX in front with TLS (Let’s Encrypt)
- Keep PostgreSQL as service or managed instance
Deploy options:
- GitHub Actions to VPS over SSH
- Docker Hub image pull on VPS
- Migration script as a deploy step
Avoid black-box deployment for long-term team growth where observability and documentation matter.
Team Docs and Knowledge Base
Nextra is a strong choice for Markdown-based docs.
Recommended documentation scope:
- Versioned API and auth flow docs
- Clerk integration guides (Next.js and Express)
- Mobile token handling reference
- Deployment runbooks (Docker, NGINX, TLS)
- Keep docs in the same repository
- CI checks for Markdown and link validation
Suggested structure:
/docs
/auth
clerk.md
tokens.md
/deploy
hostinger-vps.md
/api
nextjs.md
express.md
/mobile
kotlin-auth.mdIntegration Flow (High-Level)
- Auth
- Clerk handles authentication
- Next.js frontend uses Clerk hooks/components
- Mobile app uses Clerk SDK or REST plus JWT flow
- Backend
- Express API verifies Clerk tokens
- PostgreSQL stores application data
- Deploy
- Build Next.js and Express images
- NGINX reverse proxy in front
- TLS via Let’s Encrypt
- CI/CD scripts for push-to-deploy
- Docs
- Nextra as central team knowledge base
Final Technical Choices
- Clerk for production-ready authentication
- Transparent deployment instead of black-box tooling
- Docker with straightforward CI scripts on Hostinger VPS
- Keep deployment and auth docs in Nextra
Minimal Production Template
Includes:
- Express API
- Next.js frontend
- PostgreSQL
- Dockerized services
- NGINX reverse proxy
- GitHub Actions auto-deploy to Hostinger VPS
Project Structure
root/
├── api/ # Express backend
│ ├── Dockerfile
│ └── ...
├── web/ # Next.js frontend
│ ├── Dockerfile
│ └── ...
├── nginx/
│ └── default.conf
├── docker-compose.yml
└── .github/
└── workflows/
└── deploy.ymlExpress Dockerfile (/api/Dockerfile)
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
RUN npm run build
EXPOSE 4000
CMD ["node", "dist/index.js"]TypeScript note:
Ensure npm run build outputs to /dist.
Next.js Dockerfile (/web/Dockerfile)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]Set standalone output in next.config.js:
module.exports = {
output: 'standalone',
}NGINX Config (/nginx/default.conf)
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location /api/ {
proxy_pass http://api:4000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
proxy_pass http://web:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}Replace yourdomain.com with your real domain.
docker-compose.yml (Production)
version: "3.9"
services:
postgres:
image: postgres:15-alpine
restart: always
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: strongpassword
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
api:
build: ./api
restart: always
env_file:
- ./api/.env
depends_on:
- postgres
web:
build: ./web
restart: always
env_file:
- ./web/.env
depends_on:
- api
nginx:
image: nginx:alpine
restart: always
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- web
- api
volumes:
postgres_data:HTTPS with Let’s Encrypt
On VPS:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comContainerized certbot is also an option if TLS should stay fully container-based.
GitHub Actions Deployment
VPS preparation (one-time)
sudo apt update
sudo apt install docker docker-compose -y
sudo usermod -aG docker $USER
mkdir -p /var/www/appGitHub Secrets
Add in repository settings:
VPS_HOSTVPS_USERVPS_SSH_KEYVPS_PATH(example:/var/www/app)
.github/workflows/deploy.yml
name: Deploy to VPS
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Copy files to VPS
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
source: "."
target: ${{ secrets.VPS_PATH }}
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd ${{ secrets.VPS_PATH }}
docker compose down
docker compose build --no-cache
docker compose up -dOptional Zero-Downtime Update
Instead of:
docker compose downUse:
docker compose pull
docker compose up -d --buildThis reduces downtime.
Production Hardening Checklist
- Add
.dockerignorein bothapiandweb:
node_modules
.git
.env- Run containers as non-root users.
- Consider external PostgreSQL for scaling and resilience.
Architecture Overview
Internet
↓
NGINX (80/443)
↓
Next.js (3000)
↓
Express API (4000)
↓
PostgresOutcome
This setup gives:
- Transparent infrastructure
- Reproducible deployments
- CI/CD auto-deploy
- Clean reverse proxy layering
- Clear scaling path