Back to Article List

How to deploy SvelteKit on Coolify in 2026

How to deploy SvelteKit on Coolify in 2026

SvelteKit is a great deploy story right up until you try to put four of them on a 2 GB VPS with Coolify and they start crashing intermittently. There's a recurring pattern on the SvelteKit subreddit and on r/coolify, "I've got 5 SvelteKit apps on a Hetzner box and they keep dying," and the answer is almost always the same. The framework is fine, the build process and runtime are reasonable, the VPS is just too small for what's being asked of it.

This guide deploys a SvelteKit app on Coolify the right way, with adapter-node, sensible memory limits, a Postgres database and DNS configuration that holds up under real traffic. We'll also cover the multiple-apps-on-one-server pattern that drives most of the SvelteKit Coolify questions. Written against Coolify v4.0.0 and SvelteKit 2.x.

If Coolify isn't running yet, the LumaDock Coolify VPS ships with v4.0.0 pre-installed. Provision the VPS, follow the getting started guide for initial admin setup and the rest of this is sequential.

Pick the right adapter

SvelteKit has multiple adapters that produce different deploy artifacts. For Coolify, two are realistic.

adapter-node

This is the one you almost certainly want. It produces a Node.js server that runs your SvelteKit app with full SSR, server-side load functions, form actions, the works. It runs in any Node.js environment, which Coolify provides. Install it with npm install -D @sveltejs/adapter-node and update your svelte.config.js:

import adapter from '@sveltejs/adapter-node';

export default {
  kit: {
    adapter: adapter({
      out: 'build',
      precompress: true,
      envPrefix: 'PUBLIC_'
    })
  }
};

The precompress: true flag enables gzip and brotli pre-compression of static assets, which Coolify's Traefik will serve directly when clients ask for compressed responses. The envPrefix: 'PUBLIC_' matches SvelteKit's default convention where variables prefixed with PUBLIC_ are accessible client-side via $env/static/public.

adapter-static

Use this if your SvelteKit app is purely static (pre-rendered pages only with no server-side logic of any kind). It produces a folder of HTML, CSS and JS that you can serve from any static host. Coolify serves this through the Static Site resource type, which uses Caddy under the hood.

Most SvelteKit apps that started doing anything beyond marketing pages eventually want adapter-node, so unless you're confident the app stays static forever, just pick adapter-node and don't worry about the slightly larger deploy.

What about adapter-vercel and adapter-cloudflare?

These produce Vercel-specific or Cloudflare-Workers-specific output and are not useful on Coolify. If your svelte.config.js currently uses one of these because you were deploying to Vercel or Cloudflare, swap to adapter-node.

Step 1, prepare your repo

A few things to check before connecting Coolify.

Pin your Node.js version

Add an engines field to package.json with a sensible Node version, like "node": ">=20". Or add a .nvmrc file with just 20 in it. Coolify's Nixpacks reads either of these and picks the right Node.js version for your build. Without a pin, you'll occasionally get builds that pass on your laptop and fail on Coolify because the Node versions diverged.

Set up your environment variable conventions

SvelteKit splits env vars into four buckets:

  • $env/static/public covers variables prefixed with PUBLIC_, inlined into the client bundle at build time
  • $env/static/private covers non-prefixed variables, available only on the server, baked into the build at compile time
  • $env/dynamic/public is read at runtime, prefixed with PUBLIC_
  • $env/dynamic/private is read at runtime, server-only

This split matters for Coolify because static env vars need to be available at build time, dynamic ones don't. We'll set the right Coolify checkbox for each.

Add a build script that doesn't lie about success

Make sure npm run build exits with a non-zero code when vite build fails. The default scaffolded SvelteKit project does this correctly, but custom build scripts sometimes swallow errors. If your build script has any || true patterns, remove them. Otherwise Coolify will think the build succeeded when it didn't and start the broken container.

Step 2, set up the project and connect your repo

From the Coolify dashboard, set up a Sources entry for your Git provider if you haven't already (Sources → Add → GitHub App for GitHub, similar for GitLab/Gitea/Bitbucket). The Coolify GitHub guide walks through the App permissions and setup in detail.

Create a project (use your app's name), inside the project click Add a new resource → Private Repository (with GitHub App). Pick the repo and the production branch. Coolify scans, sees the SvelteKit dependency, picks Nixpacks Node.js as the build pack.

Step 3, configure the build and start commands

In the application's General tab, the Nixpacks-detected defaults are usually right but worth verifying:

  • Build Command: npm run build (or pnpm build, yarn build depending on your package manager)
  • Start Command: node build
  • Ports Exposes: 3000

The node build command runs the entrypoint adapter-node generated. Inside the container, it binds to 0.0.0.0:3000 by default, which Traefik then routes external traffic to.

If you set the PORT environment variable, adapter-node uses that instead of 3000. The HOST variable controls what address the server binds to. Don't set HOST=127.0.0.1, the loopback isn't reachable from outside the container. Default is 0.0.0.0 which is what you want.

Step 4, add Postgres if you need it

Most SvelteKit apps that aren't pure marketing sites need a database. Inside your project, click Add a new resource → Database → PostgreSQL. Pick a version, accept credentials, click Start. Note the resource name.

If you're using Drizzle ORM, your DATABASE_URL connection string format is:

postgresql://user:password@postgres-resource-name:5432/dbname

Use the resource name as the host. For Prisma the same URL format works. For raw pg client connections, same thing.

Add this as an environment variable in your SvelteKit app, mark it as runtime-only (uncheck Is build variable) so it doesn't get baked into the build. Database URLs almost always contain credentials and you don't want them in the build artifact.

Step 5, set environment variables with the right scope

In the application's Environment Variables tab, click Bulk Edit and paste your full .env contents. Then audit each line.

For variables prefixed with PUBLIC_ (like PUBLIC_SITE_URL, PUBLIC_POSTHOG_KEY):

  • If your code reads them via $env/static/public, leave both Is build variable and Is runtime variable checked (they're inlined at build time)
  • If your code reads them via $env/dynamic/public, you can uncheck Is build variable (they're read at runtime)

For non-prefixed variables (like DATABASE_URL, JWT_SECRET):

  • If your code reads them via $env/static/private, both checkboxes (build-time inlining)
  • If your code reads them via $env/dynamic/private, runtime only (uncheck build)

If you're not sure which API your code uses, default to runtime-only for sensitive values. Static private vars get baked into the build, which means they're in your Docker image, which means they leak if anyone has read access to the image. Dynamic private vars are read from the running container's environment, which is safer.

Also add NODE_OPTIONS with value --max-old-space-size=2560 as a build variable if you're on a 2 GB or 4 GB VPS. SvelteKit builds aren't as memory-hungry as Next.js, but Vite's bundling step still spikes RAM during the chunking phase and a 2 GB box can OOM without the hint.

Step 6, deploy and watch the logs

Click Deploy. Coolify pulls the repo, runs npm install, runs npm run build (which is Vite + the adapter-node output step), starts the container with node build. The build streams live in the Deployments tab.

First builds take 2-3 minutes for a typical SvelteKit app. Subsequent builds use the layer cache and run in 60-90 seconds. Watch for two things during the build, the vite build step finishing without errors and the adapter-node "✓ adapter complete" line confirming the server output was generated.

Once the container starts, Traefik picks it up and the app is reachable on the Coolify-generated subdomain. Hit it, verify pages render, server-side load functions return data, form actions work, dynamic routes resolve.

Step 7, add a custom domain

Open the application's Domains tab, add your real domain. Save. Update DNS to point an A record at your VPS IP. Coolify's Traefik issues a Let's Encrypt cert within a couple minutes of DNS resolving. If the cert fails to issue, the Coolify SSL guide covers the Cloudflare proxy gotcha and DNS-01 fallback.

If your SvelteKit app uses $env/dynamic/public for things like a PUBLIC_SITE_URL, update that variable in Coolify to match the new production URL. Redeploy so the running container picks it up.

Multiple SvelteKit apps on one VPS

This is the pattern that drives most of the SvelteKit-on-Coolify questions, so let's address it directly. Running 4-5 SvelteKit apps on a single 2 GB VPS will not work reliably. The reasons are basic.

Each SvelteKit app at runtime uses 80-150 MB of RAM. Five of those is 400-750 MB. Add Coolify itself (~600 MB), Postgres (200-400 MB), Redis if you have it (50-100 MB), the kernel and Docker overhead (200-300 MB) and you're at 1.5 to 2 GB before factoring in any actual application memory growth or build overhead. A 2 GB VPS will swap aggressively, the OOM killer will start picking off processes and you'll see "intermittent crashes" because that's what they look like from the outside.

The realistic floor for 4-5 SvelteKit apps with light traffic is 4 GB of RAM (the LumaDock 4 GB tier or equivalent). For 5+ apps with any database load, 8 GB is more comfortable. Builds are also where memory peaks, so even if your runtime fits, a deploy can push you over the edge if you don't have enough headroom.

If you can't upgrade the VPS, the next-best option is to limit how many apps build simultaneously. Coolify doesn't have a global concurrency setting, but you can stagger your deploys, only push to one app at a time and wait for the build to finish before triggering the next. For agencies running many small SvelteKit projects, this is workable.

The right long-term fix is the Coolify Build Server feature. Run a separate, beefier VPS as the build server, push the resulting Docker images to a registry, your runtime VPS pulls from the registry and never has to build anything itself. This keeps your runtime VPS small and predictable, lets you scale up the build VPS only when you're shipping.

Performance tuning

Static asset precompression

The precompress: true flag in your adapter-node config produces gzip and brotli versions of every static asset at build time. Traefik serves these directly when clients support them, which means your Node.js process never has to compress on the fly. This typically halves the bytes-on-the-wire for first paint.

HTTP/2 and keep-alive

Coolify's Traefik handles HTTP/2 to clients automatically, no configuration needed. The connection from Traefik to your Node.js container is HTTP/1.1 over the local Docker network, which is fine, you don't need HTTP/2 between Traefik and your app for performance.

Database connection pooling

SvelteKit's hooks system lets you create a single database connection pool that lives across requests. Don't create a new connection per request, that adds 5-15 ms of latency. With Drizzle, instantiate the client once at module scope, with Prisma, instantiate PrismaClient once at module scope. Both patterns are documented in the respective ORM docs.

For very high traffic, deploy PgBouncer as another Coolify resource between your app and Postgres. Each SvelteKit instance connects to PgBouncer, PgBouncer multiplexes onto a smaller number of actual Postgres connections, your Postgres doesn't run out of connection slots.

Common errors and how to fix them

Build fails with "JavaScript heap out of memory"

Add NODE_OPTIONS=--max-old-space-size=2560 as a build variable. If that's not enough, your VPS doesn't have enough RAM for the build, see the heap memory fix guide for swap setup or move to the Coolify Build Server pattern described above.

App starts but immediately crashes with "address already in use"

Two SvelteKit instances are trying to bind to the same port. This usually means a previous deploy didn't shut down cleanly. Stop the application from Coolify, wait 10 seconds, start it again. If it persists, check docker ps on the VPS for orphaned containers from previous deploys.

Server-side load functions throw "fetch is not defined"

You're on a Node.js version older than 18. Bump your .nvmrc or engines field to Node 20 or newer (Node 18 has fetch but it was experimental, Node 20 is stable). The Node.js 20 to 24 upgrade guide covers the upgrade path on Ubuntu/Debian.

Environment variables read as undefined in production

You're using $env/static/* APIs but the variable isn't checked as a build variable in Coolify. Static env access requires the variable to be available at build time so Vite can inline it. Either check the build variable box in Coolify or switch to $env/dynamic/* which reads at runtime.

Form actions return "405 Method Not Allowed"

Your domain is configured to redirect HTTP to HTTPS but your SvelteKit code is somehow generating HTTP URLs. Check that PUBLIC_SITE_URL uses https:// and that any custom handle hook isn't constructing URLs with the wrong protocol.

Persistent storage uploads vanish on every deploy

You don't have a Coolify Storages volume mounted on the path you're writing uploads to. SvelteKit doesn't have a built-in upload location convention, so wherever you're saving (often ./uploads/ or /data/uploads/), make sure that path is mounted as a persistent volume in Coolify's Storages settings. Or move uploads to S3-compatible storage.

Where to go from here?

Once your SvelteKit app is shipping, set up automated database backups (the Coolify database backups guide covers Postgres backups to S3-compatible storage), monitoring (the Coolify monitoring guide), security hardening (the Node.js production security guide) and PR previews if you want them (the GitHub preview deployments guide).

All of these apply equally to SvelteKit and other Node.js stacks. Good luck!

Your idea deserves better hosting

24/7 support 30-day money-back guarantee Cancel anytime
مدة الإشتراك

1 GB RAM VPS

17.38 RON Save  25 %
13.02 RON شهري
  • 1 vCPU AMD EPYC
  • 30 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • إدارة جدار الحماية
  • مراقبة مجانية

2 GB RAM VPS

26.09 RON Save  17 %
21.73 RON شهري
  • 2 vCPU AMD EPYC
  • 30 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • إدارة جدار الحماية
  • مراقبة مجانية

6 GB RAM VPS

65.28 RON Save  33 %
43.51 RON شهري
  • 6 vCPU AMD EPYC
  • 70 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • إدارة جدار الحماية
  • مراقبة مجانية

AMD EPYC VPS.P1

34.80 RON Save  25 %
26.09 RON شهري
  • 2 vCPU AMD EPYC
  • 4 GB ذاكرة RAM
  • 40 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

AMD EPYC VPS.P2

65.28 RON Save  27 %
47.86 RON شهري
  • 2 vCPU AMD EPYC
  • 8 GB ذاكرة RAM
  • 80 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

AMD EPYC VPS.P4

130.61 RON Save  20 %
104.48 RON شهري
  • 4 vCPU AMD EPYC
  • 16 GB ذاكرة RAM
  • 160 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

AMD EPYC VPS.P5

158.92 RON Save  21 %
126.26 RON شهري
  • 8 vCPU AMD EPYC
  • 16 GB ذاكرة RAM
  • 180 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

AMD EPYC VPS.P6

248.20 RON Save  21 %
195.94 RON شهري
  • 8 vCPU AMD EPYC
  • 32 GB ذاكرة RAM
  • 200 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

AMD EPYC VPS.P7

304.81 RON Save  20 %
243.84 RON شهري
  • 16 vCPU AMD EPYC
  • 32 GB ذاكرة RAM
  • 240 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

EPYC Genoa VPS.G1

21.73 RON Save  20 %
17.38 RON شهري
  • 1 vCPU AMD EPYC Gen4 معالج AMD EPYC Genoa من الجيل الرابع طراز 9xx4 بسرعة 3.25 جيجاهرتز أو ما يعادله، يعتمد على معمارية Zen 4.
  • 1 GB DDR5 ذاكرة
  • 25 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

EPYC Genoa VPS.G2

56.57 RON Save  23 %
43.51 RON شهري
  • 2 vCPU AMD EPYC Gen4 معالج AMD EPYC Genoa من الجيل الرابع طراز 9xx4 بسرعة 3.25 جيجاهرتز أو ما يعادله، يعتمد على معمارية Zen 4.
  • 4 GB DDR5 ذاكرة
  • 50 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

EPYC Genoa VPS.G4

113.19 RON Save  27 %
82.70 RON شهري
  • 4 vCPU AMD EPYC Gen4 معالج AMD EPYC Genoa من الجيل الرابع طراز 9xx4 بسرعة 3.25 جيجاهرتز أو ما يعادله، يعتمد على معمارية Zen 4.
  • 8 GB DDR5 ذاكرة
  • 100 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

EPYC Genoa VPS.G5

195.94 RON Save  33 %
130.61 RON شهري
  • 4 vCPU AMD EPYC Gen4 معالج AMD EPYC Genoa من الجيل الرابع طراز 9xx4 بسرعة 3.25 جيجاهرتز أو ما يعادله، يعتمد على معمارية Zen 4.
  • 16 GB DDR5 ذاكرة
  • 150 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

EPYC Genoa VPS.G6

213.36 RON Save  31 %
148.03 RON شهري
  • 8 vCPU AMD EPYC Gen4 معالج AMD EPYC Genoa من الجيل الرابع طراز 9xx4 بسرعة 3.25 جيجاهرتز أو ما يعادله، يعتمد على معمارية Zen 4.
  • 16 GB DDR5 ذاكرة
  • 200 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

EPYC Genoa VPS.G7

326.59 RON Save  27 %
239.49 RON شهري
  • 8 vCPU AMD EPYC Gen4 معالج AMD EPYC Genoa من الجيل الرابع طراز 9xx4 بسرعة 3.25 جيجاهرتز أو ما يعادله، يعتمد على معمارية Zen 4.
  • 32 GB DDR5 ذاكرة
  • 250 GB NVMe تخزين
  • نطاق ترددي غير محدود
  • IPv4 و IPv6 مضمّنان دعم IPv6 غير متوفر حالياً في فرنسا، فنلندا أو هولندا.
  • 1 Gbps شبكة
  • نسخ احتياطي تلقائي مضمّن
  • إدارة جدار الحماية
  • مراقبة مجانية

Frequently asked questions

Which SvelteKit adapter should I use on Coolify?

Use adapter-node for any app that needs server-side rendering, server-side load functions, form actions or API endpoints. It produces a standard Node.js server that runs in any Node environment, which Coolify provides. Use adapter-static only for purely static sites with no server logic. Don't use adapter-vercel or adapter-cloudflare, those produce platform-specific output that won't run on Coolify.

Stop installing. Start shipping.

LumaDock Coolify plans come with the dashboard pre-installed, unmetered bandwidth and a flat monthly bill. Try the server risk free with a 30-day refund guarantee.

GPU products are in high demand at the moment. Fill the form to get notified as soon as your preferred GPU server is back in stock.