DEV Community

Cover image for Svelte + PocketBase + Nginx = Amazingness
Mateusz Piórowski
Mateusz Piórowski

Posted on • Updated on

Svelte + PocketBase + Nginx = Amazingness

So it's time for the promised bonus, how to setup Svelte with PocketBase on the same server using Nginx as proxy.

This stack is perfect for any small to medium sized applications, particularly considering Svelte connecting directly to PocketBase and PocketBase's ability to work without remote SQL due to local SQLite. The performance is incredible. You can do thousands of request, and you won't even notice it.

All the code is available here:
https://github.com/mpiorowski/svelte-auth

We will be using example.com as our targeted domain.

First let's look at the architecture:

Architecture

The general idea is for the Nginx server to act as a proxy. It directs all /pb calls to the PocketBase server, while forwarding the rest to the SvelteKit server.

Folder structure

/app
/pb
.nginx.config
docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Everything is encapsuled using docker, and the easiest way to do it is by using docker-compose.

docker-compose.yml

version: "3"
services:
  app:
    container_name: svelte-auth-application
    working_dir: /app
    build:
      context: ./app

  pb:
    container_name: svelte-auth-pocketbase
    working_dir: /pb
    build:
      context: ./pb
    volumes:
      - ./pb/pb_data:/pb/pb_data
      - ./pb/pb_migrations:/pb/pb_migrations

  nginx:
    container_name: svelte-auth-nginx
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - /etc/letsencrypt:/etc/letsencrypt
    ports:
      - 80:80
      - 443:443
Enter fullscreen mode Exit fullscreen mode

Thanks to the use of docker-compose, the Docker network is automatically created. This is why services can communicate with each other using their respective service names, such as http://pb:8080 or http://app:3000.

Some key points to highlight:

  • Only Nginx exposes ports to the external world.
  • Nginx volumes are linked to the configuration file and certificate location.
  • PocketBase volumes are linked to the SQLite database and migration files."

Now, concerning SQLite files, the pb_data folder is typically ignored in the repository, while the pb_migrations are included.

Additionally, another useful SQLite feature: need to back up the database? Simply copy the pb_data folder :)

Let's dive into Dockerfiles.

pb/Dockerfile

FROM alpine:latest

ARG PB_VERSION=0.16.5

RUN apk add --no-cache \
    unzip \
    ca-certificates

ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip
RUN unzip /tmp/pb.zip -d /pb/

CMD ["/pb/pocketbase", "serve", "--http=0.0.0.0:8080"]
Enter fullscreen mode Exit fullscreen mode

app/Dockerfile

# Development
FROM node:19-alpine as dev

WORKDIR /app
COPY package.json /app/package.json

RUN npm install -g pnpm
COPY pnpm-lock.yaml /app/pnpm-lock.yaml
RUN pnpm install

COPY . .

CMD ["pnpm", "dev"]

# Build
FROM node:19-alpine as build
WORKDIR /app
COPY . .

RUN npm install -g pnpm
RUN pnpm install
RUN pnpm build

# Production
FROM node:19-alpine as prod
WORKDIR /app
COPY --from=build /app/build /app/build
COPY src /app/src
COPY package.json /app/package.json
COPY pnpm-lock.yaml /app/pnpm-lock.yaml

RUN npm install -g pnpm
RUN pnpm install --prod

CMD node build
Enter fullscreen mode Exit fullscreen mode

For those who are wondering about the Build and Production sections, these utilize a Docker multi-stage feature. This feature enables you to divide the build process and ultimately generate smaller Docker images.

https://docs.docker.com/build/building/multi-stage/

Now there is only one thing left:

nginx.conf

worker_processes 1;

events { worker_connections 1024; }

http {

    sendfile on;

    upstream docker-app {
        server app:3000;
    }

    upstream docker-pb {
        server pb:8080;
    }

    server {
        listen [::]:443 ssl;
        listen 443 ssl;
        server_name example.com;
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        location / {
            proxy_pass         http://docker-app;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }

        location /pb/_/ {
            proxy_pass         http://docker-pb/_/;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }

        location /pb/api/ {
            proxy_pass         http://docker-pb/api/;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }

    server {
        listen 80;
        server_name example.com;
        return 301 https://$host$request_uri;
    }
}
Enter fullscreen mode Exit fullscreen mode

This one is doing the important work. Firstly, it sets up an upstream to our services (take note of the app and pb service names). Then, it effectively distributes traffic based on the requested URL. Additionally, it redirects all traffic from HTTP to HTTPS.

You might notice the managed by Certbot comments. As we are exclusively running SSL, valid certificates are required on the server. The simplest approach to achieving this is by utilizing Certbot.

So on the server we need to run:

sudo certbot certonly --nginx -d example.com
sudo systemctl disable nginx
Enter fullscreen mode Exit fullscreen mode

This not only generates valid certificates but also creates the appropriate configuration for our Nginx server. We simply need to disable the default server and use the one within the Docker environment.

And that's it! One server with both PocketBase and SvelteKit irunning.

PocketBase Admin panel: example.com/pb/_/
PocketBase Api: example.com/pb/api/
SvelteKit: example.com

One last thing to remember: when making client-side requests to PocketBase, the target should be https://example.com/pb/api/. However, for server-side connections within the Docker environment, we must keep in mind that the target should be http://pb:8080. This part can be a bit confusing :).

Hope You will like it like I do! Spread the love for SQLite!

Also, like with all the articles, a shameless advertisment:

Follow me on Twitter to receive notifications. I'm also working on promoting lesser-known technologies, with Svelte, Rust, and Go being some of the main focuses.

Top comments (2)

Collapse
 
ze1enovsky profile image
Artem Zelenov • Edited

very helpful

Collapse
 
yugle7 profile image
Gleb Yuzhakov

Thanks!