SvelteKit Node App Deploy: Linux Cloud Hosting
Tags: webdev, svelte, node, javascript
Published: 2022-11-15 14:06:28

---
title: "SvelteKit Node App Deploy: Linux Cloud Hosting"
published: "true"
description: "SvelteKit node app deploy: how you can deploy your SvelteKit  app to cloud hosting like Linode with Cloudflare TLS certificates and nginx."
tags: "webdev, svelte, node, javascript"
canonical_url: "https://rodneylab.com/sveltekit-node-app-deploy/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4asd171bht5bq0vvbtoo.png"
---

## ☁️ SvelteKit Node App Deploy

In this post we look at the SvelteKit node app deploy process.  That is how you can build and serve you SvelteKit app on Linux cloud hosting.  You might ask why run your own self-managed Linux server, going for cloud hosting, rather than using **serverless**?  Well, node does no enjoy widespread support on serverless-based offerings yet your SvelteKit app might need to run in a node environment. In the example we look at, we use the sharp package to resize images and also to generate next-gen format images on our server.  This would not be possible with many serverless hosting providers.

### OK, but why not run node on a Hosting Provider?

There are also services which can host and manage node apps for you. Why not use one of those? The answer is pricing and flexibility. For our use case, we might be able to find such a host which offers what we need cheaper than Linode (the Linux cloud self-managed provider we opt for). However for another app, if you need to add a **database** or run **Redis** for caching on the server, typically you incur extra costs with a hosted solution. With Linode and similar, self-managed services, there will be no additional cost.  You just need to make sure your self-hosted Linux box has enough CPU-power and memory to run everything you are asking of it.

### Keeping things ticking over

That said, it&rsquo;s not all sunshine and rainbows! Self-hosting means you need to configure packages yourself and keep the machine up-to-date This needs a bit of Linux knowledge. This extends to setting up **SSL certificates** and such like. However we will see, setting up a reverse proxy with **nginx**, we can have **Cloudflare** generate the certificates for us. We will use a configuration which lets public users connect via the Cloudflare public domain, keeps the Linux box IP address private and only allows SSL connections into the Linux box from Cloudflare (so the **public cannot bypass Cloudflare**).

That might sound a little complicated, but we shall see if you have a little previous Linux experience, it shouldn&rsquo;t be too hard to follow along. If that all sounds exciting, then let&rsquo;s get going!

## 🧱 What are we Building?

We will host the same node app we <a href="https://rodneylab.com/svelte-css-image-slider/">created in the Svelte CSS Image Slider post</a>.  It uses the sharp package to generate images from a server endpoint. This makes it a great example to deploy to a node server.  Instead of using <abbr>DevOps<tool-tip inert role="tooltip">Developer Operations: software development and IT operations practice</tool-tip></abbr> tooling like Docker, we <strong>keep things simple</strong>, cloning the git repo for our project onto our Linux server then using <strong>pm2</strong> and nginx to run and serve the app.

![SvelteKit Node App Deploy: Screen capture shows a large image preview above of a Mayan temple. Below is a row of 5 thumbnail images.  The mouse hovers over the second which is largest and lifted higher above the others. Moving away from this larger image, in either direction, the thumbnails become progressively smaller.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqs0qtdjgsjew9m2iis3.png)

We will first update the SvelteKit app adding some **Content Security Policy** features and **HTTP security headers**. Then, we make a Linode account, and create a machine, adding node, nginx and pm2. Finally we configure everything and serve the app.

## 🚀 SvelteKit Node App Deploy: Getting Started

Let&rsquo;s start by opening up the app from the earlier previous post.  If you didn&rsquo;t <a aria-label="Open earier post where we created the app" href="https://rodneylab.com/svelte-css-image-slider/">follow along on that one</a> you can just clone the GitHub repo:

```shell
git clone https://github.com/rodneylab/sveltekit-css-hover-image-slider.git
cd sveltekit-css-hover-image-slider
pnpm install
pnpm dev
```

If you just cloned the app, it already contains the changes in the rest of this section, just read the explanations. You only need to update the code if you are carrying on from the previous post.

Start by updating `svelte.config.js` in the project root folder:

```javascript
import adapter from '@sveltejs/adapter-node';
import preprocess from 'svelte-preprocess';
import cspDirectives from './csp-directives.mjs';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://github.com/sveltejs/svelte-preprocess
	// for more information about preprocessors
	preprocess: preprocess({ postcss: true }),

	kit: {
		adapter: adapter({ precompress: true }),
		csp: {
			mode: 'hash',
			directives: cspDirectives
		}
	}
};

export default config;
```

Here we set up SvelteKit to use the node adapter.  As well as that, in lines `13`-`16`, we add Content Security Policy (CSP) hashes.  CSP directives help protect sites from cross site scripting (XSS) attacks.  These are where a nefarious actor might inject malicious code into the site before it arrives in the end user browser.  SvelteKit can automatically generate **hashes** or **nonces** which the user browser checks against the actual code it receives. If things do not checkout the browsers can block the potential threat.  We go for hashes instead of nonces because every visitor to the site will see the same content and we want to cache it.  Nonces are cheaper to generate but should not be reused across requests. You can <a aria-label="Open post on Svelte Kit Content Security Policy" href="https://rodneylab.com/sveltekit-content-security-policy/">see more on CSP directives and XSS attacks in the post on SvelteKit Content Security Policy</a>.

### CSP Directives Config File

Next we can add the `csp-directives.mjs` file referenced in the `svelte.config.js` file above:

```javascript
const cspDirectives = {
	'base-uri': ["'self'"],
	'child-src': ["'self'"],
	'connect-src': ["'self'", 'ws://localhost:*'],
	// 'connect-src': ["'self'", 'ws://localhost:*', 'https://hcaptcha.com', 'https://*.hcaptcha.com'],
	'img-src': ["'self'", 'data:'],
	'font-src': ["'self'", 'data:'],
	'form-action': ["'self'"],
	'frame-ancestors': ["'self'"],
	'frame-src': [
		"'self'"
		// "https://*.stripe.com",
		// "https://*.facebook.com",
		// "https://*.facebook.net",
		// 'https://hcaptcha.com',
		// 'https://*.hcaptcha.com',
	],
	'manifest-src': ["'self'"],
	'media-src': ["'self'", 'data:'],
	'object-src': ["'none'"],
	'style-src': ["'self'", "'unsafe-inline'"],
	// 'style-src': ["'self'", "'unsafe-inline'", 'https://hcaptcha.com', 'https://*.hcaptcha.com'],
	'default-src': [
		'self'
		// 'https://*.google.com',
		// 'https://*.googleapis.com',
		// 'https://*.firebase.com',
		// 'https://*.gstatic.com',
		// 'https://*.cloudfunctions.net',
		// 'https://*.algolia.net',
		// 'https://*.facebook.com',
		// 'https://*.facebook.net',
		// 'https://*.stripe.com',
		// 'https://*.sentry.io',
	],
	'script-src': [
		'self'
		// 'https://*.stripe.com',
		// 'https://*.facebook.com',
		// 'https://*.facebook.net',
		// 'https://hcaptcha.com',
		// 'https://*.hcaptcha.com',
		// 'https://*.sentry.io',
		// 'https://polyfill.io',
	],
	'worker-src': ["'self'"]
};

export default cspDirectives;
```

This config works fine for this app. For your own app you will almost certainly need to adjust the config. **Test CSP directives attentively** as they can completely block access to your site.  It is also worth using them in conjunction with reporting, so you are more likely to find out about something going wrong with the config.  Sentry offer free reporting.  If you have not used CSP directives before, it might be prudent to limit using them to pet projects.

### HTTP Security Headers

SvelteKit lets us specify HTTP security header in the `src/hooks.server.ts` files

```typescript
import type { Handle } from '@sveltejs/kit';
 
export const handle: Handle = async function handle({ event, resolve }) {
	const response = await resolve(event);
	response.headers.set('X-Frame-Options', 'SAMEORIGIN');
	response.headers.set('Referrer-Policy', 'no-referrer');
	response.headers.set(
		'Permissions-Policy',
		'accelerometer=(), autoplay=(), camera=(), document-domain=(), encrypted-media=(), fullscreen=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()'
	);
	response.headers.set('X-Content-Type-Options', 'nosniff');
	// response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
	return response;
};
```

You will probably need to <a aria-label="Open article introducing the Perissions Policy" href="https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/">tweak the Permissions Policy here</a> for your own projects. When we serve this HTTP header, the browser checks the policy before allowing access to the camera, for example.  The X-Frame Options helps <a href="https://www.imperva.com/learn/application-security/clickjacking/">protect against <abbr>clickjacking<tool-tip inert role="tooltip">Tricking a visitor to clicking an invisible or hidden element to trigger the download of malicious code</tool-tip></abbr></a>. `Referrer-Policy` limits the information sent on when the user navigates to another website.


![SvelteKit Node App Deploy: Screen capture shows security headers result. Result is A with check marks for Content-Security-Policy, Permissions-Policy, Referrer-Policy, X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4p3x2uep5mnb1ks3hgmk.png)

We can also set HTTP headers on the Linux box in the nginx config. In fact, we will set `Strict-Transport-Security` there so commented it out above.

## ☑️ Local test build

Once you are happy with the configuration you can run a local test build:

```shell
pnpm build
```

This will build a standalone app, to the `build` directory. The app  will run in node .  To test it run:

```shell
PORT=4173 node build
```

By default it will run on port `3000` so if you already have something running there, add the `PORT=4173` variable ahead of the node comand, like we have above. If all is well, create a git repo and push the code to your GitHub, GitLab or other git service.  We will clone from there onto our self-managed Linux box later.


## 🌩 Create Linode Volume

We are all set to go cloud-side now. If you <a aria-label="Create a new Linode account" href="https://login.linode.com/signup">do not yet have a Linode account, you can create one for free</a>.  If you are a Syntax podcast listener, get a special deal, using the link they include in Linode-sponsored episodes.  You will get an initial credit which will covers hosting initially. Do not forget to switch you box off if you are just testing a pet project, so you do not get a surprise bill when credit runs out!

Now you have a Linode account, <a href="https://cloud.linode.com/linodes/create">create a new Linux server instance</a>.  From the options, I chose the following:

- Image: Ubuntu 22.04 LTS,
- Region: choose one which makes sense for you,
- Linode Plan: Shared CPU / Nanode 1GB,
- Add-ons: I did not select anything here


![SvelteKit Node App Deploy: Screen capture shows choices in the Linode/Create console. The Ubuntu 22.04 LTS has been chosen. Shared CPU: Nanode 1 G B is also selected.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwzbs2udnwb5cu7to55m.png)

Feel free to customise to your own tastes, though if you choose another Linux distribution, the commands and instructions may differ form the ones I tested below.

Next <a aria-label="Read Linode docs on S S H keys" href="https://www.linode.com/docs/guides/use-public-key-authentication-with-ssh/">create an SSH key locally</a> then copy the public key. Select **Add an SSH key** and paste the public key into the **SSH Public Key** box which appears.  Add a suitable **Label** too.  Unfortunately I was&rsquo;t able to <a href="https://rodneylab.com/using-fido-u2f-github-ssh/">make a secure key SSH key (e.g. `ed25519-sk`) work</a>. You should be able to use a regular `rsa`, `ecdsa`, `ed25519` key though.

Finally click the **Create Linode** button at the bottom of the window.

## 🛠 Linux Server Setup

It will take a few minutes for the server to spin up. Go to the <a href="https://cloud.linode.com/linodes">Linodes section of the console to see you new instance details</a>.  This will list the IP address. For convenience, you might want to add this to the `~/.ssh/config` file on your system:

```plaintext
Host linode
        Hostname                111.222.222.111
        User                    root
        ControlMaster           no
        IdentitiesOnly          no
        Ciphers                 chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
        MACs                    hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
        IdentityFile            ~/.ssh/path-to-your-new ssh-key
 
Host *
        AddressFamily                   inet
        HashKnownHosts                  yes
        VisualHostKey                   yes
        PasswordAuthentication          no
        ChallengeResponseAuthentication no
        StrictHostKeyChecking           ask
        VerifyHostKeyDNS                yes
        ForwardAgent                    no
        ForwardX11                      no
        ForwardX11Trusted               no
        ServerAliveInterval             300
        ServerAliveCountMax             2
        KexAlgorithms                   curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
        HostKeyAlgorithms               ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256
```

Replace the IP address in the Hostname field (line `2`) with the actual IP address of your self-hosted Linux server (from the Linode console).  Also update the path to you SSH private key in line `8`.

Finally try connecting to the box, from the Terminal:

```shell
ssh linode
```

![SvelteKit Node App Deploy: Screen capture of Terminal after S S H login. The readout starts Welcome to Ubuntu 22.04.1 LTS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ey3hqs6j9i4vdbplg6ja.png)
<Image client:load {...imageProps[3]} />

### 💫 System Update

Next let&rsquo;s update the system and add the packages we need:

```shell
sudo apt update
sudo apt upgrade
sudo apt install nginx ufw
```

Then, we can install the LTS version of node.  The other packages were already in the apt package repository, but the node version in apt is probably quite old. We can add a current LTS version to the apt repo by running this command:

```shell
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
```

Check this command against the node docs to reassure yourself it is safe to run a shell script. Finally we can have apt install node for us:

```shell
sudo apt-get install -y nodejs
```

## 🔥 Firewall Config

We will use Universal firewall as it is fairly straightforward to configure.  I would also recommend using the firewall on the Linode console. This protects you if for some reason ufw stops.  Anyway for ufw run:

```shell
ufw allow ssh
ufw app list
```

This should have enabled ufw for ssh, which we need to connect to the box. Then the second command listed the apps available.  I got:


```plaintext
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH
```

we will go for 'Nginx Full'. Only Cloudflare will be able to establish http connections and it will use TLS.  Select this option from the Terminal:

```shell
sudo allow 'Nginx Full'
sudo ufw enable
sudo ufw status
```

With the Firewall configured let&rsquo;s set up nginx.


## 🚒 nginx config

nginx is a web server. You might ask why we need this since locally, we just ran `node build` and the app was available on port `4173`.  The reason is we don&rsquo;t want public visitors to connect to the self-managed Linux server directly.  This helps us with security.

We will set up a domain on Cloudflare. The visitors will connect to Cloudflare and Cloudflare will pass the request on to our box. Our self-managed Linux server will then pass the request to the SvelteKit app running locally and relay the response to Cloudflare.  nginx is the middle man here. Essentially providing a pipe to connect Cloudflare with our SvelteKit node app.  The configuration we opt for is known as a **reverse proxy**.

### Reverse Proxy Configuration

To start lets make an nginx config file.  Update the domain from example.com to your actual domain, this will help you identify the right config if you have multiple sites on your box later:

```shell
sudoedit /etc/nginx/sites-available/example.com
```

This used nano as the default editor, which (strangely) I found difficult now I use vim most of the time.  Feel free to open in vim if you find that easier to use!  Either way add this content:

```plaintext
proxy_cache_path /data/nginx/cache      levels=1:2      keys_zone=STATIC:10m inactive=24h max_size=1g;
 
server {
        listen 80;
        listen [::]:80;
        server_name example.com;
        return 302 https://$server_name$request_uri;
}
 
server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
 
        ssl_certificate         /etc/ssl/cert.pem;
        ssl_certificate_key     /etc/ssl/key.pem;
        ssl_client_certificate  /etc/ssl/authenticated_origin_pull_ca.pem;
 
        ssl_session_timeout     1d;
        ssl_session_cache       shared:MozSSL:10m;
        ssl_session_tickets     off;
			 
        ssl_protocols   TLSv1.3;
        ssl_prefer_server_ciphers       off;
 
        add_header      Strict-Transport-Security       "max-age=63072000" always;
  
        # OCSP stapling
        ssl_stapling    on;
        ssl_stapling_verify     on;
        ssl_trusted_certificate /etc/ssl/origin_ca_ecc_root.pem;
 
        ssl_verify_client       on;
 
        server_name example.com;
				server_tokens		off; # hide nginx version

				location / {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $host;
                proxy_buffering on;
                proxy_cache     STATIC;
                proxy_cache_valid       200 1d;
                proxy_cache_use_stale   error timeout invalid_header updating http_500 http_502 http_503 http_504;
                proxy_pass http://localhost:3000/;
        }
}
```

- The first line adds a <a aria-label="Open n gin x docs on reverse proxy with cache example" href="https://www.nginx.com/resources/wiki/start/topics/examples/reverseproxycachingexample/">static cache for performance</a>, this is fine for our app, but you might want to remove it if you have a Server-Side Rendered app with personalised content.
- in lines `3`&thinsp;&ndash;&thinsp;`8`: we redirect any HTTP request to HTTPS requests, for security,

The remaining code handles SSL requests.

- in lines `14`&thinsp;&ndash;&thinsp;`16` we add links to the SSL certificates. We will get those from Cloudflare in the next section,
- lines `18`&thinsp;&ndash;&thinsp;`32` list further security features. These mostly originate from the <a href="https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=modern&openssl=1.1.1k&guideline=5.6">Mozilla SSL Configuration Generator tool</a>,
- finally the code in lines `36`&thinsp;&ndash;&thinsp;`46` is setting up our reverse proxy. Essentially any incoming requests to paths starting &lsquo;`/`&rsquo; (which will be any request for our app), will get passed to `http://localhost:3000`. Remember `3000` is the default port the SvelteKit node app runs on. 


### Enabling the Site

We added that file to `sites-available`.  To enable it, we can <a aria-label="Read more about Linux symbolic links" href="https://www.freecodecamp.org/news/symlink-tutorial-in-linux-how-to-create-and-remove-a-symbolic-link/">create a symbolic link</a> to that file within the adjacent `sites-enabled` folder:

```shell
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
```

There was already a `/etc/nginx/sites-enabled/default` file for me which I deleted. That was so that nginx uses our new file instead of the default.

## 😶‍🌫️ Cloudflare Config

We&rsquo;re assuming you already own the domain you want to use and have it setup in Cloudflare Registrar.  I think you can buy domains directly in Cloudflare now. An alternative is to use a service like Hover, normally pricing (for buying domains) is quite reasonable. Once you buy the domain there, you have to wait a certain period before you can transfer it into Cloudflare (60 days comes to mind), but it is worth setting a calendar reminder, as you will probably save a bit on annual renewals.

### SvelteKit Node App Deploy: Cloudflare DNS

Anyway first we will add an A Record in the Cloudflare <abbr>DNS<tool-tip inert role="tooltip">Domain Name Service</tool-tip></abbr> console. An A record translates a domain to an IP address. Because we want to keep our self-managed Linux cloud server IP secret though, we will also use the Cloudflare proxy.  That means when a visitor browser looks up your site&rsquo;s domain, it will get a Cloudflare IP address. Under the hood, Cloudflare translates that to our Linux self-managed IP address (keeping it private throughout).

From the <a aria-label="Open the Cloudflare dashboard" href="https://dash.cloudflare.com">Cloudflare dashboard</a>, choose **Websites** then click the domain your site will be hosted on.  Then select **DNS** from the menu, and **Add Record**.  Ensure the proxy is enabled with **Proxied** displayed in the Proxy status column (as in screenshot). The IP address here is that of your Linux box.

![SvelteKit Node App Deploy: Screen capture of Cloudflare console shows D N S. Proxy status is enabled and statesProxied.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/waebx7al1f3l2shr1x7g.png)

### SvelteKit Node App Deploy: SSL Certificates

Cloudflare generates the SSL certificates for us (needed for `/etc/ssl/cert.pem` and `/etc/ssl/key.pem`) which we saw in the nginx config file.  We also had a couple more files in there. `/etc/ssl/authenticated_origin_pull_ca.pem` is a trusted Cloudflare  certificate. Including this tells nginx only to accept connections from servers with certificates issues by this Certificate authority.  Essentially, this limits us to accepting connections from Cloudlfare only, into nginx. Remember public visitors will connect to Cloudflare and our reverse proxy looks after their request from that point on.  Finally we had `/etc/ssl/origin_ca_ecc_root.pem` which is used for <abbr>OCSP<tool-tip inert role="tooltip">Online Certificate Status Protocol</tool-tip></abbr> stapling. OCSP stapling provides a private way of letting browsers check for revoked certificates. 

Let&rsquo;s start with the first two. Go **SSL/TLS** and select **Origin Server** from the submenu. Then click **Create Certificate**.  We will use **Generate private key and CSR with Cloudflare**. Choose from RSA or ECC and add hostnames in the box (e.g. `*.example.com` `example.com`). Finally click **Create**. Your new certificates will be displayed.  Keep the default PEM format. Copy the contents from **Origin Certificate**  (including `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----`) and paste them into a new file on the Linux self-managed server: `/etc/ssl/cert.pem`. Make sure there are no extra empty lines at the start or end of the file. Then copy the contents of **Private Key** to `/etc/ssl/key.pem`.  Check <a aria-label="OpenCLoud flare pull authenticated pull origin docs" href="https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/set-up">Cloudflare docs for further details</a>.

![SvelteKit Node App Deploy: Screen capture of Cloudflare console shows S S L / T L S section with Origin Server Selected. In the main window, under Origin Certificates, there is a large Create Certificate button in the top section. In the bottom section titled Authenticated Pulls, a tick or check mark is visible.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/air7etyeh1wx6c71lnk9.png)

### Authenticated Origin and OCSP Stapling

Next make sure **Authenticated Origin Pulls** is selected.  You can <a href="https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem">download the Authenticated Origin Pull Certificate from Cloudflare `authenticated_origin_pull_ca.pem`</a>.  Lookup a link in official Cloudflare docs if you prefer.  You can download directly to the box:

```shell
curl -LO https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
sudo mv authenticated_origin_pull_ca.pem /etc/ssl/.
```

Then, you can download the certificate used for OCSP stapling:

```shell
curl -LO https://developers.cloudflare.com/ssl/static/origin_ca_ecc_root.pem
sudo mv origin_ca_ecc_root.pem /etc/ssl/.
```

Finally make the directory we gave for caching in the nginx config and check the nginx config is good:
```shell
mkdir -p /data/nginx
nginx -t
```

Now check if nginx is already running:

```shell
systemctl status nginx
```

If it is running, restart it:

```shell
sudo systemctl restart nginx
```

Otherwise start and enable it (so it restarts automatically if the machine reboots):

```shell
sudo systemctl enable nginx
sudo systemctl start nginx
```

That&rsquo;s the Cloudflare setup complete. Next we spin up the app.


## ♥️ SvelteKit Time

Clone the repo from your GitHub (update the URL unless you want clone the Rodney Lab one):

```shell
git clone https://github.com/rodneylab/sveltekit-css-hover-image-slider.git
cd sveltekit-css-hover-image-slider
npm install
npm run build
```

Although I normally prefer pnpm, I have just opted for npm command here as it is already installed and the internet connection should be pretty fast anyway!

```shell
npm i pm2 -g
pm2 start build/index.js --name svelte-app
```

This should install pm2 and start the app running. To check, run `pm2 ls`. You should see something like this:


![SvelteKit Node App Deploy: Screen capture shows Terminal result of running pm2 ls. svelte-app is listed as online.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ff0ubtli57qzzythsac.png)

Also try running:

```shell
curl -L https://127.0.0.1:3000
```
if you need to debug. This will print the page HTML to the Terminal if the app is up and running.

For your e2e user test, you can open your site in the browser from the public URL (using the domain name) and hopefully all is well for you!  Also try connecting from your browser to the IP address of the self-managed Linux server (e.g. `https://111.222.222.111`). I get a `400: No required SSL certificate was sent` error.  This suggests the public will not be able to access the directly: exactly what we want.


## 💫 SvelteKit Node App Deploy: Updates (Continuous Integration)

Like your serverless apps, you can update the repo locally and then push to your git repo.  After that, you will probably want to SSH in and run this sequence of commands:

```shell
cd sveltekit-css-hover-image-slider
git pull
npm run build
pm2 restart svelte-app
sudo rm -r /data/nginx/cache/*
sudo systemctl restart nginx
```

This clears the nginx cache and restarts nginx to make sure everything is consistent.

## 💯 SvelteKit Node App Deploy: Testing

We checked the page works at the end of a previous section. You might also want to check the HTTP security headers. Both <a aria-label="Open Security Headers" href="https://securityheaders.com/">SecurityHeaders.com</a> and <a aria-label="Open the Mozilla Observatory" href="https://observatory.mozilla.org/">Mozilla Observatory</a> are good for this.  You might not be able to get an A+ on both because SvelteKit does not add style CSP hashes (at the time of writing).  Instead we used the `style-src: unsafe-inline` directive.  CSS hashes are important, though; maliciously injected CSS could hide an important warning you included in your site.

![SvelteKit Node App Deploy: Screen capture shows Mozilla Observatory result. Result is A+ with a score of 110/100 and Tests Passed in 11/11.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hv7ywf8vj8bk6rt2m7g0.png)

## 🙌🏽 SvelteKit Node App Deploy: Wrapping Up

In this post, we saw the SvelteKit node app deploy process.  In particular, we saw:

- how to configure **HTTP headers in SvelteKit**,
- how to use **Cloudflare to stop the public accessing the self-managed Linux server**,
- a possible **nginx configuration considering security**.

Please <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/sveltekit-css-hover-image-slider">see the full repo code on the Rodney Lab GitHub repo</a>. Hope you have found this post on the SvelteKit node app deploy process useful and learned at least one thing.  Please let me know about any possible improvements to the content above.

## 🙏🏽 SvelteKit Node App Deploy: Feedback

If you have found this post useful, see links below for further related content on this site. I do hope you learned one new thing from the video. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on Twitter, giving me a mention so I can see what you did. Finally be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as <a href="https://rodneylab.com/tags/seo/" aria-label="See posts on search engine optimisation">Search Engine Optimisation</a> among other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.