What actually runs on a 2 GB VPS has a dull answer if you read system requirements and a useful one if you measure. We took one Flare VPS (1 vCPU, 2 GB RAM, 25 GB disk), installed Docker, and ran the app stacks from our own guides: each alone, then side by side, then with a swap file.

Every number here is idle. The apps were started, checked on their health endpoints and left alone. No logins, no imported data, no load tests. Real use costs more, so read these as a floor.

Using an AI coding agent? There's a ready-made prompt at the end of this guide. Copy that instead of this article.

The short answer

What we measured, and what we left out

Nine stacks from the compose files in our published guides: Uptime Kuma, Vaultwarden, Navidrome, Audiobookshelf, Jellyfin, Forgejo, n8n (with Postgres), Home Assistant (with Mosquitto) and Plausible Community Edition (with Postgres and ClickHouse). Immich was tested separately with its official compose file, since it is a different weight class. There is no "Immich lite", so we did not invent one.

Three things were left out:

Results: each stack alone

Each stack started from a clean state three times, with containers and volumes removed between runs. "First start" runs from docker compose up -d to the first successful HTTP answer, images already pulled. "Idle RAM" is the rise in the host's used figure in free -m after two minutes idle: median of three, range in brackets.

Stack Containers Image size Pull First start Idle RAM Data on disk after start
Vaultwarden 1 383 MiB 12.5 s 1.3 s under 30 MiB 1 MiB
Navidrome 1 332 MiB 12.7 s 0.8 s under 30 MiB 1 MiB
Audiobookshelf 1 499 MiB 16.2 s 2.5 s (2.4-4.7) 43 MiB (28-54) 0-18 MiB
Forgejo 1 266 MiB 10.3 s 1.8 s (1.8-2.5) 112 MiB (102-120) 117 MiB
Uptime Kuma 1 2,372 MiB 74.1 s 4.0 s (3.7-4.5) 98 MiB (95-106) 0 MiB
Jellyfin 1 2,385 MiB 82.8 s 3.4 s (2.0-3.5) 139 MiB (137-161) 1 MiB
Home Assistant + Mosquitto 2 3,348 MiB 91.6 s 8.9 s (8.5-8.9) 346 MiB (342-359) 45 MiB
n8n + Postgres 2 2,133 MiB 74.0 s 21.0 s (20.5-21.4) 389 MiB (365-406) 117 MiB
Plausible + Postgres + ClickHouse 3 1,337 MiB 38.2 s 29.8 s (29.8-29.9) 610 MiB (603-613) 53 MiB
Caddy (reference) 1 89 MiB 4.1 s 0.8 s 6 MiB (-12 to +19) 0 MiB

The control. To learn what the method can resolve, we ran it on a plain nginx:alpine container three times. It came out at -19 MiB (range -41 to +2). A container cannot use negative memory, so that spread is noise: about plus or minus 40 MiB. That is why Vaultwarden and Navidrome say "under 30 MiB" (their cgroup counters later read 6 and 9 MiB). docker stats also disagreed with itself between runs, with ClickHouse at 163 MiB in one and 495 in another, because it counts file cache. We trust the host figure and say where it is soft.

Results: how many fit together

We added the stacks one at a time, smallest first, let each settle for a minute and recorded memory. The rules: stop at about 90 percent used, and skip anything projected past 95 percent.

Added Started in Used after Available after
(Docker only) 441 MiB 1,526 MiB
Vaultwarden 1.2 s 452 MiB 1,515 MiB
Navidrome 1.2 s 474 MiB 1,493 MiB
Audiobookshelf 2.7 s 546 MiB 1,421 MiB
Forgejo 1.7 s 646 MiB 1,321 MiB
Uptime Kuma 4.2 s 732 MiB 1,235 MiB
Jellyfin 1.9 s 853 MiB 1,114 MiB
Home Assistant + Mosquitto 8.8 s 1,160 MiB 807 MiB
n8n + Postgres 21.5 s 1,575 MiB 392 MiB
Two minutes later 1,542 MiB 425 MiB
Plausible skipped, projected 110%

Eight stacks, ten containers, all answering. The rise (1,542 minus 441 MiB) is 1,101 MiB, and the eight stacks measured alone sum to 1,113 MiB, so you can budget a box by adding the single numbers. Pressure was close to nil: the kernel's stall counter read 0.07 percent over a minute, with no swap writes and no kills.

Next in line was Plausible at about 610 MiB against 425 MiB available. We did not start it without swap. The projection said something would have to be killed, and we had no reason to find that out the hard way.

The effect of a 2 GB swap file

With the eight stacks still running we created a 2 GB swap file (fallocate, mkswap, swapon, default swappiness of 60) and started Plausible.

Moment Used Available Swap used
Eight stacks, swap on, nothing changed 1,543 MiB 424 MiB 0
Plausible started (took 31.8 s; 29.8 s alone) 1,396 MiB 571 MiB 817 MiB
Four minutes later 1,338 MiB 629 MiB 892 MiB
Eight minutes later 1,299 MiB 668 MiB 955 MiB

Nine stacks, thirteen containers, all answering. "Available" went up after adding a stack, which looks wrong until you see why: the kernel moved memory idle processes had not touched out to the swap file and kept active file cache in RAM. n8n's private memory fell from 329 to 72 MiB, Jellyfin's from 74 to 16, Uptime Kuma's from 96 to 9.

The cost shows up as pressure. While Plausible started, the stall counter peaked at 1.5 percent over a minute, then fell to 0.05 by minute four. In eight minutes about 1.4 GB was written to swap and 0.5 GB read back. With 953 MiB swapped out we requested each health endpoint three times: first answers took 0.7 to 117 ms, later ones under 10 ms. That is a ping, not a page render, so it shows only that nothing hung.

What swap is. A safety net that lets cold memory leave RAM so the active set fits. It is not more RAM. A workload that actively uses 2.5 GB will thrash, and on one vCPU everything slows together.

One mistake worth sharing. Afterwards we ran swapoff while all nine stacks were still up. It has to pull everything back into RAM, and RAM was full, so the out-of-memory killer ended the swapoff process and nothing else. Stopping the stacks recovered the box in seconds. Stop things before you remove swap from a loaded server.

Immich: the one that doesn't fit

Immich's documentation lists 6 GB of RAM as the minimum and 8 GB as recommended (docs.immich.app, requirements page). We tested it instead of taking that on trust.

Alone, without swap, the official compose stack answered its ping at 24 s. Seconds later its server container reached about 1 GiB at 95 percent CPU, host used hit 1,381 MiB and available memory fell to 38 MB. A watchdog stopped the stack before the kernel had to. Nothing was killed.

With the swap file the same start survived. It peaked at 1,759 MiB used with 208 MiB available and 548 MiB swapped (stall counter 9.1 percent over ten seconds), then settled at 1.3 to 1.5 GiB used with about 515 MiB swapped. That was an empty Immich: no photos, no recognition running. Our Immich guide recommends a larger plan for this reason.

What not to run on 2 GB

This is judgement from the numbers above, not extra measurement.

What fits comfortably: Uptime Kuma, Vaultwarden, Navidrome, Audiobookshelf and Forgejo together use under 400 MiB at idle.

Disk is the other limit

The nine stacks' images total 12.7 GiB on a 25 GB disk where Ubuntu and Docker already hold about 2.3 GiB. That leaves about 8.5 GiB for data, backups and logs. The big ones are Home Assistant (3.3 GiB), Jellyfin and Uptime Kuma (2.3 GiB each) and n8n with Postgres (2.1 GiB). Prune old images after updates.

How we measured

A Flare VPS on Ubuntu 24.04.3, kernel 6.8.0-88-generic, Docker Engine 29.9.2 and Compose v5.6.1, on 2026-10-22. To repeat it:

  1. Install Docker with the apt-repository method from Docker's documentation.
  2. Use the guides' compose files minus Caddy, one folder (one Compose project) per stack. Vaultwarden gets DOMAIN=http://localhost and no admin token. Plausible gets BASE_URL=http://localhost and a generated secret.
  3. Per stack, from clean: remove containers and volumes, drop the page cache (echo 3 > /proc/sys/vm/drop_caches), record free -m, run docker compose up -d, and poll the container's health URL every half second until it answers 2xx or 3xx. That is "first start".
  4. Wait 120 s, drop the cache, record free -m and docker stats --no-stream three times. Memory is the rise in used.
  5. Repeat three times and report median and spread.
  6. Run a control (nginx:alpine). Its result is your noise floor.
  7. Combined run: add stacks smallest first, a minute of settling each, logging free -m, /proc/pressure/memory and the swap counters in /proc/vmstat. Run a watchdog that stops the last stack if available memory drops under 60 MB, and lower sshd's OOM score so your session survives.

Not measured: any load, real data, a second server, memory creep over days, and CPU beyond seeing it near zero at idle.

Do it with an AI agent

Don't paste the article at Claude Code, Cursor or another agent: it will skim the cautions and run the risky step first. Copy this instead to measure your own box, and run it from your own machine with your agent able to SSH out.

Prompt for an AI agent
Measure the idle memory, disk and start time of docker compose stacks on a fresh
Ubuntu 24.04 VPS, and report how many fit together. Idle measurements only.

FILL IN BEFORE YOU START:
- SERVER_IP = <VPS IP from the PrivateByte dashboard>
- STACKS    = <list of compose folders I will provide, one folder each>
- I will provide the compose files. Do not invent or download others.

WHAT TO DO:
1. SSH to root@SERVER_IP. Report "free -m", "df -h /" and "swapon --show".
2. Install Docker Engine using Docker's apt repository method from
   https://docs.docker.com/engine/install/ubuntu/ (not the convenience script).
   Report "docker --version" and "docker compose version".
3. Lower the SSH daemon's OOM score so the session survives pressure:
   for each sshd pid, write -900 to /proc/PID/oom_score_adj.
4. CONTROL FIRST: run an nginx:alpine container three times with the
   procedure in step 5. Report the spread. That is the noise floor.
5. For each stack, alone, three times: remove containers and volumes; drop the
   page cache; record "free -m"; time "docker compose up -d" until its health
   URL answers 2xx or 3xx (poll every 0.5 s, use the container's Docker network
   address); wait 120 s; drop the cache; record "free -m" and
   "docker stats --no-stream" three times, 10 s apart. Idle RAM is the rise in
   "used". Report median and range. Also record image size and disk used.
6. COMBINED: add the stacks smallest first, one at a time, wait 60 s after
   each, and record "free -m" and /proc/pressure/memory. BEFORE each add,
   estimate the new memory from the single-stack figures. If the projected
   total is over 95 percent of RAM, STOP and tell me. Never go past 90 percent
   used without asking.
7. Only if I say yes: create a 2 GB swap file (fallocate, chmod 600, mkswap,
   swapon), then continue adding stacks, recording swap used and pressure.

RULES:
- Idle measurements only. No load tests, no benchmarks, no traffic generation.
- Never create accounts, log in, or enter passwords in any app. Check health
  URLs and HTTP codes only.
- Never print or log secret values. If a stack needs a generated secret, create
  it with openssl and check that it is non-empty.
- Do not use swapoff while stacks are running. Stop the stacks first.
- Do not trigger an out-of-memory kill on purpose. Add a watchdog that stops
  the most recently added stack if "MemAvailable" falls under 60 MB.
- Nothing destructive outside the stack folders. If a folder already holds data
  I did not ask you to remove, STOP and ask.
- Do not edit the compose files except to remove a Caddy service that needs a
  domain, and tell me which ones you changed.

VERIFY, AND SHOW ME THE OUTPUT OF EACH:
- "docker ps" -> expected containers running, none restarting
- the health URL answer and HTTP code for each stack
- "free -m" before and after each step, and "cat /proc/pressure/memory"
- "dmesg | grep -ci 'out of memory'" before and after -> must not rise
- the control run's spread, stated next to the results it applies to

Do not tell me a step succeeded without showing the command output that proves
it. If a verification fails, stop and report the actual error. Do not retry
silently and do not improvise a workaround.

Two rules carry the weight. The 90 percent limit and the watchdog exist because the failure on a small server is a killed app, not a slow one, and the measurement should stop first. The control comes before the results because without it a small number is an unlabelled guess.

Deploy your VPS

A password manager, a status page, a music server and a Git host sit comfortably on 2 GB. Add a heavier stack and you want the next size up, with a swap file as insurance.

The Flare plan is where these numbers were measured.

Flare plan
$5.99/mo
1 vCPU · 2 GB RAM · 25 GB SSD
Deploy a Flare VPS
  • Unmetered bandwidth, no overage
  • Free DDoS protection
  • Daily automated backups
  • Browser console access
Ready in under 60 seconds. No contract, cancel any time.

Common questions

Can I run several Docker apps on a 2 GB VPS? Yes. Eight stacks (ten containers) ran together at idle with about 425 MB of RAM to spare: Vaultwarden, Navidrome, Audiobookshelf, Forgejo, Uptime Kuma, Jellyfin, Home Assistant and n8n.

Should I add a swap file on a 2 GB server? It is cheap insurance. With a 2 GB swap file the ninth stack, Plausible, ran, and about 950 MiB of idle memory moved to disk. It is not extra RAM, and active workloads over 2 GB will slow down.

How much RAM does Immich need? Its documentation says 6 GB minimum, 8 GB recommended. On our 2 GB box it reached 38 MB available in under a minute without swap.

Do these numbers include a reverse proxy? No. Caddy added about 6 MiB when measured alone, within the noise.

Are these numbers for a busy server? No. Everything is idle, with empty libraries and no users. Treat them as minimums.