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:
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
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
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"]
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
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; } }
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
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 :).
End
Hope you enjoyed it!
Like always, a little bit of self-promotion :)
Follow me on Github and Twitter to receive new notifications. I'm working on promoting lesser-known technologies, with Svelte, Go and Rust being the main focuses.
I am also a creator of GoFast, the ultimate foundation for building modern web apps with the power of Golang and SvelteKit / Next.js. Hop in if you are interested :)
Top comments (3)
very helpful
Why not just run pocketbase alongside node + sveltekit and just connect to it using its default port?
Thanks!