Skip to content

DigitalOcean 2 GB — the floor

The whole product (API + Postgres + storefront + admin + Caddy) on the smallest supported box, no Redis/Celery. This is the official minimum spec, and the measured verdict is honest: supported, with the swap caveat.

  • Target: DigitalOcean Basic droplet, 2 GB RAM / 1 vCPU / 50 GB SSD (~$12/mo; the 2 GB / 2 vCPU option works the same).
  • Profile: lite.
  • Time: ~30 minutes. You need: this repo, a domain, three DNS records.

Get the server: Get your DigitalOcean Droplets here.

  1. Create the droplet.

    Open DigitalOceanCreate → Droplet:

    • Image: Ubuntu 24.04 LTS
    • Plan: Basic → Regular → 2 GB RAM / 1 vCPU (or 2 GB / 2 vCPU)
    • Authentication: an SSH key if you have one (recommended — the hardening guide shows how to create one), otherwise a strong root password.

    Create it, wait for it to boot, and note the droplet’s IP address.

  2. Point your domain at the droplet.

    Add three A records at your DNS provider — @ (storefront), api (the API) and admin (staff admin app), each with value = the droplet IP, TTL 300. Verify with ping your-domain.com and ping api.your-domain.com. Do this before first boot so the HTTPS certificates can be issued automatically.

  3. Log in, add swap, install Docker.

    Terminal window
    ssh root@YOUR_DROPLET_IP

    Swap first. At the 2 GB floor, swap protects against a one-off spike (a huge import, a burst of image processing) turning into an OOM kill. The stack doesn’t live in swap — it just makes the worst case degrade gently:

    Terminal window
    fallocate -l 2G /swapfile
    chmod 600 /swapfile
    mkswap /swapfile
    swapon /swapfile
    echo '/swapfile none swap sw 0 0' >> /etc/fstab
    sysctl vm.swappiness=10 && echo 'vm.swappiness=10' >> /etc/sysctl.conf
    free -h # should now show 2.0Gi swap

    (swappiness=10 means “use swap only under pressure”, so normal traffic stays in RAM.) Then Docker:

    Terminal window
    apt-get update && apt-get -y upgrade
    curl -fsSL https://get.docker.com | sh
    docker --version && docker compose version
  4. Get the code.

    Terminal window
    apt-get -y install git
    git clone YOUR_REPO_URL /srv/app
    cd /srv/app

    (Or upload your purchase zip — the Hostinger guide, step 4 shows the scp + unzip variant; the steps are identical.)

  5. Configure .env.

    Terminal window
    cp .env.example .env
    nano .env

    Minimum edits (same as the Hostinger guide):

    • DJANGO_SECRET_KEY — generate: docker run --rm python:3.13-slim python -c "import secrets; print(secrets.token_urlsafe(64))"
    • DOMAIN=your-domain.com — the shop domain (the api. / admin. hosts, allowed hosts, CORS and CSRF derive from it; for the split topology, set the API host instead)
    • DB_PASSWORD — a strong password
    • DJANGO_SUPERUSER_USERNAME / _EMAIL / _PASSWORD — your admin login

    Keep INFRA_PROFILE=lite (the default) — that’s the whole point of this box. Save (Ctrl+O, Enter) and exit (Ctrl+X).

  6. Launch (lite is the default compose file).

    Terminal window
    docker compose up -d

    First run builds the image (5–10 minutes on 1 vCPU — be patient). Boot is fully automatic: wait-for-db → migrate → collectstatic → superuser → serve.

    Terminal window
    docker compose ps # wait for web "(healthy)"
    docker compose logs -f web # Ctrl+C to stop

    You’ll see seven containers: caddy (the only one with public ports), web, postgres, scheduler (cron jobs), backup (nightly database dump, 7-day rotation), plus the two prebuilt Next.js apps storefront and admin. Every one runs under a hard memory limit totalling ~1.9 GB — tight on 2 GB, which is exactly why step 3’s swap is mandatory here.

  7. Verify.

    Terminal window
    curl -s https://api.your-domain.com/health/ # → "status": "success"
    free -h # see the actual usage
    docker stats --no-stream # per-container memory

    Open https://api.your-domain.com/admin/ and log in.

Prefer real headroom at 2 GB? Keep only the API + database on this small box and host the two Next.js apps on a free/cheap frontend platform (they’re standard Next.js apps):

Terminal window
docker compose -f docker-compose.yml -f docker-compose.api-only.yml up -d

This scales the storefront / admin containers to 0 and swaps Caddy to the single-domain layout (DOMAIN serves the API directly). Point the hosted apps at the box via their own runtime env (API_URL / API_PUBLIC_URL) and add their origins to DJANGO_TRUSTED_CORS_ORIGINS / DJANGO_CSRF_TRUSTED_ORIGINS. With this layout you only need the api DNS record on the droplet.

  • Email/SMS are sent inline with strict 2-second timeouts in lite — configure a real SMTP provider in .env (DJANGO_EMAIL_*); failures are retried automatically every 10 minutes from the outbox.
  • Heavy image uploads are bounded (10 MB / 40 MP — oversized uploads get a clean error instead of eating your RAM).
  • If you outgrow the box (sustained traffic, real-time needs), don’t tune — move up: an 8 GB VPS + the full profile is a one-flag change.

The Hostinger troubleshooting table applies here too, plus one floor-specific check: if a container was killed, docker inspect <name> --format '{{.State.OOMKilled}}' tells you if it hit its memory limit. With swap enabled and stock settings, this should never happen — it’s exactly what the shipped resource budget verifies.