Skip to Content
chalvien 1.0 is released

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 (or systemd services 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.md

Integration Flow (High-Level)

  1. Auth
    • Clerk handles authentication
    • Next.js frontend uses Clerk hooks/components
    • Mobile app uses Clerk SDK or REST plus JWT flow
  2. Backend
    • Express API verifies Clerk tokens
    • PostgreSQL stores application data
  3. Deploy
    • Build Next.js and Express images
    • NGINX reverse proxy in front
    • TLS via Let’s Encrypt
    • CI/CD scripts for push-to-deploy
  4. 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.yml

Express 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.com

Containerized 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/app

GitHub Secrets

Add in repository settings:

  • VPS_HOST
  • VPS_USER
  • VPS_SSH_KEY
  • VPS_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 -d

Optional Zero-Downtime Update

Instead of:

docker compose down

Use:

docker compose pull docker compose up -d --build

This reduces downtime.

Production Hardening Checklist

  1. Add .dockerignore in both api and web:
node_modules .git .env
  1. Run containers as non-root users.
  2. Consider external PostgreSQL for scaling and resilience.

Architecture Overview

Internet NGINX (80/443) Next.js (3000) Express API (4000) Postgres

Outcome

This setup gives:

  • Transparent infrastructure
  • Reproducible deployments
  • CI/CD auto-deploy
  • Clean reverse proxy layering
  • Clear scaling path