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)
Why not just run pocketbase alongside node + sveltekit and just connect to it using its default port?
very helpful
Thanks!