Pocketbase As A Framework: Deploy With Front- and Backend

2 Comments
Modified: 15.03.2023

You build your application using Pocketbase as a Framework, and now you want to bring it to production and host it on your server. In this guide, we will look at 2 ways to deploy pocketbase as the backend and 3 ways how to deploy it together with a frontend. As a little bonus, I also added simple docker-compose files and GitHub Actions.

Backend

Fullstack

Deploy Pocketbase as a Framework using Docker

This method is suitable for you if you only want to host a pocketbase backend and use Docker on your server.

FROM golang:1.18 AS build-backend

RUN mkdir /app
ADD . /app
WORKDIR /app

RUN CGO_ENABLED=0 GOOS=linux go build -o <binary-name> .

FROM alpine:latest AS production
COPY --from=build-backend /app .
EXPOSE 8080
CMD ["./<binary-name>", "serve", "--http=0.0.0.0:8080"]

This is a simple docker-compose file to run the container on your server. After running it, you can access the pocketbase instance under IP:8080.

You can also make it accessible through a domain by pointing an A record to the IP of your server and using a reverse proxy to direct the traffic to the correct container. In this post, I teach you how to use an automatic reverse proxy with SSL support for your new applications: here.

Need help or want to share feedback? Join my discord community!

services:
  backend:
    container_name: backend
    image: <registry>/<image-name>
    ports:
      - "8080:8080"
    volumes:
      - ./data:/pb_data

The GitHub Action connects to your Server using SSH and then pulls and builds the new version of the image using docker-compose. You might need to adapt the GitHub Action to your environment, but the Dockerfile will help you with every environment using Docker.

name: publish

on:
  push:
    branches: [ "main" ]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: <image-name>

jobs:
  publish:
    name: publish image
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Login
      run: |
        echo ${{ secrets.PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
    - name: Build and Publish Backend
      run: |
        docker build . --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
        
  deploy:
    needs: publish
    name: deploy image
    runs-on: ubuntu-latest
    
    steps:
    - name: install ssh keys
      # check this thread to understand why its needed:
      # <https://stackoverflow.com/a/70447517>
      run: |
        install -m 600 -D /dev/null ~/.ssh/id_rsa
        echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
        ssh-keyscan -H ${{ secrets.SSH_HOST }} > ~/.ssh/known_hosts
    - name: connect and pull
      run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd ${{ secrets.WORK_DIR }} && docker compose pull && docker compose up -d && exit"
    - name: cleanup
      run: rm -rf ~/.ssh

Deploy Pocketbase with a Frontend using two Containers

This method is suitable for you if you only want to host both a pocketbase backend and a frontend, for example, using vue. Additionally, you need to use Docker on your server. Splitting the frontend and backend into different images is useful if you want to run multiple frontend instances on your server or use a solution like Kubernetes with auto-scaling.

KOFI Logo

If this guide is helpful to you and you like what I do, please support me with a coffee!

# build stage
FROM node:lts-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production stage
FROM nginx:stable-alpine as production
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM golang:1.18 AS build-backend

RUN mkdir /app
ADD . /app
WORKDIR /app

RUN CGO_ENABLED=0 GOOS=linux go build -o <binary-name> .

FROM alpine:latest AS production
COPY --from=build-backend /app .
EXPOSE 8080
CMD ["./<binary-name>", "serve", "--http=0.0.0.0:8080"]

This is a simple docker-compose file to run the containers on your server. After running it, you can access the pocketbase instance under IP:8080, and the frontend instance under IP:3000.

You can also make them accessible through a domain by pointing an A record to the IP of your server and using a reverse proxy to direct the traffic to the correct container. In this post, I teach you how to use an automatic reverse proxy with SSL support for your new applications: here.

services:
  frontend:
    container_name: frontend
    image: <registry>/<image-name>-frontend:latest
		ports:
			- "3000:3000"

  backend:
    container_name: backend
    image: <registry>/<image-name>-backend:latest
    ports:
      - "8080:8080"
    volumes:
      - ./data:/pb_data

The GitHub Action connects to your Server using SSH and then pulls and builds the new versions of the images using docker-compose. You might need to adapt the GitHub Action to your environment, but the Dockerfile will help you with every environment using Docker.

name: publish

on:
  push:
    branches: [ "main" ]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: <image-name>

jobs:
  publish:
    name: publish image
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Login
      run: |
        echo ${{ secrets.PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
    - name: Build and Publish Backend
      run: |
        docker build ./backend --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend:latest
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend:latest
    - name: Build and Publish Frontend
      run: |
        docker build ./frontend --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:latest
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:latest
        
  deploy:
    needs: publish
    name: deploy image
    runs-on: ubuntu-latest
    
    steps:
    - name: install ssh keys
      # check this thread to understand why its needed:
      # <https://stackoverflow.com/a/70447517>
      run: |
        install -m 600 -D /dev/null ~/.ssh/id_rsa
        echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
        ssh-keyscan -H ${{ secrets.SSH_HOST }} > ~/.ssh/known_hosts
    - name: connect and pull
      run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd ${{ secrets.WORK_DIR }} && docker compose pull && docker compose up -d && exit"
    - name: cleanup
      run: rm -rf ~/.ssh

Deploy Pocketbase with a Frontend using one Docker image (⭐ Docker)

This method suits you if you want to host both the backend and frontend using one docker image. In the example, before, we used NGINX to serve the frontend, and in this case, we use pocketbase itself to do so. For this to work, we must add a simple route to the main.go file:

app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
		// serves static files from the provided public dir (if exists)
		e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), true))
		return nil
})

Here it’s important to note that the true add the end sets index.html as the fallback for all routes. If you get 404 errors when moving to different routes while having this option on false, you can fix that by setting it to true.

FROM node:lts-alpine as build-frontend
WORKDIR /app
COPY ./frontend/package*.json ./
RUN npm install
COPY ./frontend/ .
RUN npm run build

FROM golang:1.18 AS build-backend

RUN mkdir /app
ADD ./backend /app
COPY --from=build-frontend /app/dist /app/pb_public
WORKDIR /app

RUN CGO_ENABLED=0 GOOS=linux go build -o <binary-name> .

FROM alpine:latest AS production
COPY --from=build-backend /app .
EXPOSE 8080
CMD ["./<binary-name>", "serve", "--http=0.0.0.0:8080"]

This is a simple docker-compose file to run the container on your server. After running it, you can access the pocketbase instance under IP:8080/_ and the frontend under IP:8080/. You can also make it accessible through a domain by pointing an A record to the IP of your server and using a reverse proxy to direct the traffic to the correct container. In this post, I teach you how to use an automatic reverse proxy with SSL support for your new applications: here.

services:
  backend:
    container_name: backend
    image: <registry>/<image-name>
    ports:
      - "8080:8080"
    volumes:
      - ./data:/pb_data

The GitHub Action connects to your Server using SSH and then pulls and builds the new version of the image using docker-compose. You might need to adapt the GitHub Action to your environment, but the Dockerfile will help you with every environment using Docker.

name: publish

on:
  push:
    branches: [ "main" ]

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: <image-name>

jobs:
  publish:
    name: publish image
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Login
      run: |
        echo ${{ secrets.PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
    - name: Build and Publish Backend
      run: |
        docker build . --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
        docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
        
  deploy:
    needs: publish
    name: deploy image
    runs-on: ubuntu-latest
    
    steps:
    - name: install ssh keys
      # check this thread to understand why its needed:
      # <https://stackoverflow.com/a/70447517>
      run: |
        install -m 600 -D /dev/null ~/.ssh/id_rsa
        echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
        ssh-keyscan -H ${{ secrets.SSH_HOST }} > ~/.ssh/known_hosts
    - name: connect and pull
      run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd ${{ secrets.WORK_DIR }} && docker compose pull && docker compose up -d && exit"
    - name: cleanup
      run: rm -rf ~/.ssh

Conclusion

There are multiple different methods to bring your Pocketbase Application to production. In this post, we learned how to do so by using Docker and rsync. I hope this post was helpful to you, and if there are any questions, feel free to contact me!

And also, in case you liked this post, consider subscribing to my newsletter.

[convertkit form=2303042]

Discussion (2)