Hostinger KVM 2 — the flagship path
The whole product on one box: storefront + staff admin app + API + database behind Caddy with automatic HTTPS. This is the flagship path — the full profile (Redis + Celery + websockets), measured comfortable at this tier.
- Target: Hostinger KVM 2 — 2 vCPU, 8 GB RAM, 100 GB NVMe (~$7–9/mo).
- Time: ~30–40 minutes, most of it waiting for installs.
- You need: this repo (your purchase download or git access), a domain name, and the ability to add three DNS records.
Get the server: Hostinger KVM 2 VPS
-
Buy the VPS and pick the OS.
Open Hostinger KVM 2 and complete the purchase, then in the setup flow (hPanel → VPS → Setup):
- Operating system: Ubuntu 24.04 LTS — the plain OS, not a template with a control panel.
- Set a strong root password, and add your SSH key if you have one (you can add it later — the hardening guide shows how to make one).
Wait for the VPS to reach Running, then note its IP address from the dashboard.
-
Point your domain at the server.
The box serves the whole product on three hostnames, so add three A records at your DNS provider:
Type Name Value TTL Serves A @(bare domain, or ashopsubdomain)your VPS IP 300 storefront A apiyour VPS IP 300 Django API A adminyour VPS IP 300 staff admin app We’ll use
your-domain.comin the examples — substitute yours everywhere. DNS usually propagates in minutes; check withping your-domain.comandping api.your-domain.comuntil they answer with the VPS IP. -
Log in and install Docker.
From your laptop’s terminal (Windows: PowerShell or PuTTY):
Terminal window ssh root@YOUR_VPS_IPOn the server:
Terminal window apt-get update && apt-get -y upgradecurl -fsSL https://get.docker.com | shdocker --version && docker compose version # both should print versions -
Get the code.
Clone your repository:
Terminal window apt-get -y install gitgit clone YOUR_REPO_URL /srv/appcd /srv/app…or upload your purchase zip and unpack it:
Terminal window # from your laptop:scp product.zip root@YOUR_VPS_IP:/srv/# on the server:apt-get -y install unzip && mkdir -p /srv/app && unzip /srv/product.zip -d /srv/app && cd /srv/app -
Configure
.env.Terminal window cp .env.example .envnano .envChange at least these values (search with
Ctrl+Win nano):Variable Set it to DJANGO_SECRET_KEYa fresh random key — generate on the server: docker run --rm python:3.13-slim python -c "import secrets; print(secrets.token_urlsafe(64))"DOMAINyour-domain.com— the shop domain; theapi.andadmin.hosts, allowed hosts, CORS and CSRF all derive from it automaticallyDB_PASSWORDa strong database password DJANGO_SUPERUSER_USERNAME/_EMAIL/_PASSWORDyour admin login (created automatically on first boot) Optional now, easy later (all runtime — see Branding & domains):
APP_NAME,BRAND_ACCENT/BRAND_LOGO_URL, provider keys for payments / SMS / couriers (the.envcomments name each), andADMIN_ENABLED_DOMAINS. Brand and theme are best set in the tenant config after first boot.Leave
DJANGO_ENV=productionandDJANGO_DEBUG=Falseas they are. Save and exit:Ctrl+O,Enter,Ctrl+X. -
Launch (full profile).
Terminal window docker compose -f docker-compose.yml -f docker-compose.full.yml up -dFirst run builds the API image, pulls the prebuilt storefront + admin images, and starts everything — expect 5–10 minutes. The web container waits for the database, migrates, collects static files and creates your superuser by itself; there are no manual steps. The two Next.js apps are configured entirely by
.envat start-up — never rebuilt.Watch it come up:
Terminal window docker compose logs -f web # Ctrl+C to stop watchingdocker compose ps # wait for web: "(healthy)" -
Verify.
Terminal window curl -s https://api.your-domain.com/health/ # APIcurl -s https://your-domain.com/api/config # storefront runtime configcurl -s https://admin.your-domain.com/api/config # admin runtime configThe first shows
"status": "success"; the other two return the public runtime config each app derived from your.env— all over real Let’s Encrypt certificates Caddy obtained automatically. Then open your shop (https://your-domain.com), the staff app (https://admin.your-domain.com), and the deep-ops console (https://api.your-domain.com/admin/; log in with the superuser from.env).
If something doesn’t work
Section titled “If something doesn’t work”| Symptom | Likely cause / fix |
|---|---|
| Browser warns about the certificate | DNS wasn’t pointing at the VPS when Caddy started. Fix the A record, then docker compose restart caddy. |
curl: connection refused |
Ports blocked — check docker compose ps shows caddy Up, and no firewall is blocking 80/443 (see hardening; Hostinger’s hPanel firewall must also allow 80/443). |
DisallowedHost error page |
Your domain isn’t in DJANGO_ALLOWED_HOSTS → edit .env and docker compose up -d web. |
| web container restarts repeatedly | docker compose logs web — most often a missing/weak DJANGO_SECRET_KEY (production refuses keys under 50 chars) or a DB password mismatch after an earlier attempt (docker compose down -v wipes the old database volume, then up -d again — only safe before you have real data). |
| Want to seed a demo catalog to click around | docker compose exec web python manage.py seed_client --profile belltex --demo |