├── .dockerignore ├── .gitattributes ├── .gitignore ├── Dockerfile ├── README.md ├── fly.toml ├── pb_data.tar.gz └── run.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | /.git 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | pb_data.tar.gz filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pb_data -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | 3 | ARG PB_VERSION=0.16.6 4 | ARG MARMOT_VERSION=v0.8.4-beta.6 5 | 6 | RUN apt update && apt install unzip dnsutils sqlite3 curl wget htop -y 7 | 8 | 9 | # download and unzip PocketBase 10 | COPY run.sh /pb/run.sh 11 | COPY pb_data.tar.gz /pb/pb_data.tar.gz 12 | ADD https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip /tmp/pb.zip 13 | ADD https://github.com/maxpert/marmot/releases/download/${MARMOT_VERSION}/marmot-${MARMOT_VERSION}-linux-amd64.tar.gz /tmp/marmot.tar.gz 14 | RUN unzip /tmp/pb.zip -d /pb/ 15 | RUN mkdir -p /tmp/marmot && \ 16 | cd /tmp/marmot && \ 17 | tar vxzf /tmp/marmot.tar.gz && \ 18 | mv /tmp/marmot/marmot /pb/marmot && \ 19 | cd /pb && \ 20 | rm -rf /tmp/marmot 21 | 22 | RUN chmod +x /pb/run.sh 23 | 24 | EXPOSE 8080 25 | 26 | # start PocketBase 27 | CMD ["/pb/run.sh"] 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Combining Marmot + PocketBase + Fly.io 2 | 3 | ## What is Marmot? 4 | 5 | [Marmot](https://github.com/maxpert/marmot) is an distributed SQLite replicator that runs as a side-car to you service, and replicates data across cluster using NATS. 6 | 7 | ## What is PocketBase? 8 | [PocketBase](https://github.com/pocketbase/pocketbase) is an open source backend consisting of embedded database (SQLite) with realtime subscriptions, built-in auth management, convenient dashboard UI and simple REST-ish API. 9 | 10 | ## What is Fly.io? 11 | Fly is a platform for running full stack apps and databases close to your users. Compute jobs at Fly.io are virtualized using Firecracker, the virtualization engine developed at AWS as the engine for Lambda and Fargate. 12 | 13 | ## Why should I care? 14 | This demo effectively shows how PocketBase can be pushed closer to the edge. After developer has done local development, and finalized schema, a literal copy of the DB can be deployed in production. These nodes scale up or down based on traffic, and write from everywhere. This can horizontally scales your PocketBases close to the user. With NATS embedded into Marmot, a sharded RAFT is used to capture changes, and replay them across the fly nodes (multi-primary replicas). 15 | 16 | ## Important Notes: 17 | - **Cluster instances have to start with same DB snapshot** - Since Marmot doesn't support schema level change propagation, 18 | tables, indexes you will be creating, deleting won't be picked up. Marmot only transports data right now! This 19 | repo ships with sample data snapshot that was created using local PocketBase instance, so it should give you 20 | good starting point. You only need schema of tables + indexes in order to see replication working. This should 21 | not be a no big deal because one can easily write a script to apply migrations (recommended way), use the 22 | backup to import old data, and deploy it as part of Docker image. 23 | - **Change propagation is dependent on PocketBase committing to disk** - Marmot can only propagate changes that are written 24 | to disk! Marmot does not use any hooks or anything into PocketBase process. As a matter of fact Marmot doesn't 25 | even care whats running along side with it. 26 | - **This example doesn't use persistent volume** - Base snapshot and logs in Marmot nodes should be enough to get you 27 | up and running every-time. You can configure Marmot with S3/Minio snapshots for higher reliability. 28 | 29 | ## Install Flyctl 30 | 31 | - Follow the installation instructions from https://fly.io/docs/hands-on/install-flyctl/. 32 | - Run `fly auth signup` to create a Fly.io account (email or GitHub). 33 | - Run `fly auth login` to login. 34 | 35 | ## Deploy and Scale 36 | 37 | - Create Fly app using `fly app create`, fill in the information on prompts. 38 | - Deploy on app using `fly deploy -a `, here `application-name` will be the name of app you created 39 | - Scale the app to multiple pods you `fly scale count 3 -a `. At least have 2 pods for Marmot to 40 | start a cluster (in current configuration), otherwise Marmot would keep waiting for more nodes to come up. 41 | 42 | ## Create Admin 43 | 44 | Once cluster is started go to `http://.fly.dev/_/` to launch admin panel, it will prompt you to create an 45 | admin account. Choose your email and password. Once you hit create, it will create your admin account. 46 | 47 | > PocketBase might show you an error saying invalid token. If that happens just wait for a second or so to let 48 | > changes propagate. Try reloading `http://.fly.dev/_/` until you see login form. If issue 49 | > persists try creating account again. 50 | 51 | ## Use the APIs 52 | 53 | Now you can play with your app's API using `http://.fly.dev/api/`. Checkout 54 | [PocketBase Docs](https://pocketbase.io/docs/) for deep dive. -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | kill_signal = "SIGINT" 2 | kill_timeout = 5 3 | processes = [] 4 | region = "ams" 5 | 6 | [experimental] 7 | allowed_public_ports = [] 8 | auto_rollback = true 9 | 10 | # optional if you want to change the PocketBase version 11 | [build.args] 12 | PB_VERSION="0.19.3" 13 | MARMOT_VERSION="v0.8.6" 14 | 15 | [[services]] 16 | http_checks = [] 17 | internal_port = 8080 18 | processes = ["app"] 19 | protocol = "tcp" 20 | min_machines_running = 2 21 | auto_stop_machines = false 22 | auto_start_machines = true 23 | script_checks = [] 24 | [services.concurrency] 25 | hard_limit = 25 26 | soft_limit = 20 27 | type = "connections" 28 | 29 | [[services.ports]] 30 | force_https = true 31 | handlers = ["http"] 32 | port = 80 33 | 34 | [[services.ports]] 35 | handlers = ["tls", "http"] 36 | port = 443 37 | 38 | [[services.tcp_checks]] 39 | grace_period = "1s" 40 | interval = "15s" 41 | restart_limit = 0 42 | timeout = "2s" 43 | -------------------------------------------------------------------------------- /pb_data.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpert/marmot-pocketbase-flyio/dada5410a781d8c4e92ad9e583415092ea8a7254/pb_data.tar.gz -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd /pb 4 | cat << "EOD" 5 | ___ ___ _ 6 | | \/ | | | 7 | | . . | __ _ _ __ _ __ ___ ___ | |_ 8 | | |\/| |/ _ | '__| '_ _ \ / _ \| __| 9 | | | | | (_| | | | | | | | | (_) | |_ 10 | \_| |_/\__,_|_| |_| |_| |_|\___/ \__| 11 | 12 | This data is sample database from http://2016.padjo.org/files/data/starterpack/ssa-babynames/ssa-babynames-nationwide-since-1980.sqlite 13 | (Marmot doesn't support schema changes replication, so make sure it boots with same DB state everywhere) 14 | Database was prepared and imported into local PocketBase 15 | EOD 16 | tar vxzf ./pb_data.tar.gz 17 | 18 | 19 | /pb/pocketbase serve --http=0.0.0.0:8080 & 20 | PB_ID=$! 21 | 22 | # Generate Node ID 23 | NODE_ID=$(echo -n "$FLY_MACHINE_ID" | md5sum | cut -d' ' -f1 | rev | cut -c1-8 | tr -d '\n' | od -A n -vt u8) 24 | 25 | 26 | MARMOT_CONFIG=$(cat << EOM 27 | db_path="/pb/pb_data/data.db" 28 | node_id=${NODE_ID} 29 | 30 | [replication_log] 31 | shards=1 32 | replicas=2 33 | max_entries=1024 34 | compress=true 35 | 36 | [logging] 37 | format="console" 38 | EOM 39 | ) 40 | 41 | # Enable disable snapshots based on WEBDAV_URL 42 | if [ -z "$WEBDAV_URL" ]; then 43 | MARMOT_CONFIG="${MARMOT_CONFIG}"$(cat << EOM 44 | 45 | [snapshot] 46 | enable=false 47 | EOM 48 | ) 49 | else 50 | MARMOT_CONFIG="${MARMOT_CONFIG}"$(cat << EOM 51 | 52 | [snapshot] 53 | enable=true 54 | store='webdav' 55 | interval=3600_000 56 | 57 | [snapshot.webdav] 58 | url="${WEBDAV_URL}" 59 | EOM 60 | ) 61 | fi 62 | 63 | echo "$MARMOT_CONFIG" > ./marmot-config.toml 64 | 65 | # Start marmot in a loop 66 | while true; do 67 | sleep 1 68 | 69 | # Launch! 70 | echo "Launching marmot ..." 71 | GOMEMLIMT=32MiB \ 72 | /pb/marmot -config ./marmot-config.toml -cluster-addr "[${FLY_PRIVATE_IP}]:4222" -cluster-peers "dns://global.${FLY_APP_NAME}.internal:4222/" & 73 | MARMOT_ID=$! 74 | 75 | # Wait for marmot to exit 76 | wait $MARMOT_ID 77 | 78 | # Restart Marmot 79 | echo "Marmot needs to be running all the time, restarting..." 80 | sleep 1 81 | done 82 | 83 | 84 | # Define a cleanup function 85 | cleanup() { 86 | echo "Caught signal, stopping." 87 | kill $PB_ID $MARMOT_ID 88 | } 89 | 90 | # Set the trap 91 | trap cleanup TERM INT KILL 92 | 93 | wait $PB_ID $MARMOT_ID 94 | --------------------------------------------------------------------------------