(smollerGraph)
  • Episode 08
  • 9 min

Deploying a graph database to a VPS with Docker

smollerGraph on a VPS, sent over SSH and run with Docker Compose, with Caddy serving the page in front of the database.

22 October 2026 · Watch on YouTube

We put smollerGraph on a VPS with a small script, deploy.sh, which sends the committed code over SSH, and Docker Compose builds it there and runs it in two containers. Caddy serves the page and passes the API's requests on to sg serve, which nothing outside can reach. Then we open the UI from anywhere, walk the food chain, inspect a record and ask a question in Cypher, all on the server.

The code

Each step on screen is a tag in the database's repository. The code at the end of the episode is ep008/end.

  1. 2:15ep008/step-1DeployingBrowseChanges
  2. 4:34ep008/step-2Caddy in frontBrowseChanges

Transcript

Intro

0:00 Hello and welcome back to episode eight, where we're going to put smollerGraph on a server, so its page answers from anywhere, and not from my machine alone. Let's begin with a quick look at where we're going to be by the end of the episode.

0:14 By the end, the page we built is going to be served from a VPS, a server I rent, at its own address. It answers Cypher there, down the food chain to the clover and the wheat, from the server's own copy of the graph.

0:28 On the website of the series, you can find more information about the project. The code is open source on GitHub, tagged at each step of each episode, so that you can follow along, and each episode has a page here, with its chapters and its code.

Last time

0:42 Now, before we get to today's work, let's quickly go over what we did last time. A lexer split a Cypher query into tokens, and a recursive descent parser read them into a query. A planner made steps named after Neo4j's, seeking the start in our index, and expanding a hop at a time. An executor ran them on rows, and sg query and the page both answer our subset of Cypher. All of it runs on my machine, at localhost, where nobody else can open it. Today, it goes on a server, for anyone to open.

Today

1:15 Today, we're going to deploy smollerGraph, so its page is served from a server. The server is a VPS, a virtual machine I rent, which already runs Docker. A small script sends it our code over SSH, and Docker Compose builds it there and runs it. On a server, the page and the database go in containers of their own, with a web server, Caddy, in front. Then we open the page from the server's address, and use it as we did on my machine. All right, let's get started with the server.

The server

1:44 For the server, we're going to use the VPS I rent from DreamHost, which runs the game server from my MMO series, and the app from mvGPT, the GPT series on this channel. It has two virtual CPUs and four gigabytes of memory, which is plenty for our animals, and for a graph a good deal bigger. It came with Docker installed, so our image builds and runs there as it does on my machine. There's a link to it in the description, if you'd like one of your own; it's an affiliate link, which supports the channel.

Deploying

2:15 Deploy dot sh, like the scripts in my other two series, takes the server, as a user at an address, and its comment shows how it's called. Set dash e u o pipefail stops the script at the first command that fails, so a failed copy doesn't go on to a build. Server is the script's first argument, the user and the address. Git archive packs the committed code, as a tar stream, and the pipe sends it over SSH. There, the old copy is removed, and tar unpacks the new one into a folder called smollerGraph, in the home folder on the server, as the comment above it sums up. Removing the old folder first means a file we've deleted from the repo doesn't stay behind, and as git archive packs what's committed, a change I haven't committed doesn't reach the server either. Then, over SSH again, docker compose up builds the image there, from the Dockerfile's build stage, and swaps the running container for one from it, as the second comment says, on this step. Dash d runs it in the background, dash dash build builds the image first, and dash dash wait waits until the container is running, so the script ends with the server up. In compose dot yaml, the comment gets a sentence about the server: deploy dot sh runs it there, and it starts again when the server restarts. That's restart unless stopped, which brings the container back after a reboot, unless we stopped it ourselves. The rest stays as it was: the build stage, the animals loaded into a fresh graph, and sg serve on all of the container's addresses, with port eight four seven four published on the server's. And the README has a section on deploying, with what the server needs, Docker with Compose, and a note that the page reads the graph, so there's nothing to change through it.

Caddy in front

3:57 Until now, sg serve did everything: it sent the page's files, and answered the API. On a server, it's usual to split those up. A browser talks to a web server, Caddy, in a container of its own, which is all that's published on the server's port. Caddy serves the page's files on its own, the HTML, the style sheet and the script, as a web server is built to. And it passes the requests under slash A P I on to sg serve, in a second container. The database isn't published at all, so the web server is all that can reach it, and the one place to add HTTPS or limits later.

4:34 The Caddyfile is Caddy's configuration, and its comment says what this one does: it's the web server in front of the database, serving the page's files on its own, and passing the API's requests on to sg serve. The site block is colon eight four seven four, which listens on that port with plain HTTP, as there's no name to get a certificate for yet. Root sets the folder the files come from, slash srv, and file server serves them, with index dot html for slash. Reverse proxy sends anything under slash A P I to sg, port eight four seven four, by the name of its service, which Docker's network turns into the other container's address. In compose dot yaml, the comment says there are two containers now, and that both start again when the server restarts. The sg service keeps its build and its command, and its comment says it's on the network the two containers share, and not published on the machine, so its ports line is gone. The new service, web, is Caddy's official image, pinned to version two point ten point two, on Alpine, which keeps it small. Its comment says where its files come from. The volumes mount the Caddyfile from the repo where Caddy looks for one, and the U I folder as slash srv, both of them read-only. Ports publishes eight four seven four on the machine, now from Caddy. Depends on starts sg first, and restart unless stopped brings it back after a reboot. In deploy dot sh, the comments say it runs behind Caddy now, and that the deploy swaps the running containers, plural. And the README says what runs where, and on which port.

6:13 Let's deploy it, to the VPS's address, as the debian user. The script sends the code, and Docker Compose builds the image on the server, from the toolchain up: Ubuntu, a compiler, CMake and Ninja, then Abseil, GoogleTest, cpp-httplib and smollerGraph. On two cores, the first build takes a couple of minutes, so the video skips ahead. Docker keeps each step of a build, so the next deploy builds again from the first step whose files changed. Docker p s, on the server, shows the two containers. The web container is up, with port eight four seven four published on all of the server's addresses. Docker publishes ports past ufw, the server's firewall, so the page can be reached as soon as it runs. The database's container is up too, with no ports at all. And from my machine, curl asks the server for the graph, through Caddy, which comes back with the red fox first, as it did at localhost.

On the server

7:10 Here's the page, at the server's address, on port eight four seven four. The graph settles as it did on my machine. A walk from the red fox lights up the food chain, a hop at a time, and clicking the fox shows its record, as the server keeps it on its own disk. And a question in Cypher, where the tawny owl's prey lives, comes back as the field mouse, in the meadow, parsed, planned and run by the database, behind Caddy.

What's next

7:36 This is a start, and there's more to do before the server holds data that matters. The page is plain HTTP, on a port of its own. With a name pointing at the server, our Caddy could get a certificate for it, and serve it over HTTPS, as the MMO's Caddy does. The page reads the graph and changes nothing, which is why it's fine to leave open, and writing through it would need someone to be allowed to. And a graph that's written to needs to survive a crash in the middle of a write, which is what a write-ahead log is for, and where we're heading.

Wrap-up

8:08 So, today we put smollerGraph on a server. Deploy dot sh sends the committed code to a VPS over SSH, and Docker Compose builds it there and runs it, starting it again when the server reboots. Caddy serves the page and passes the API on to the database, which nothing else can reach. And the page we built answers from the server's address, walks, records and Cypher included.

8:32 This is going to be an evolving project, so I'd love to hear from you. Let me know in the comments where you'd host it, and if you'd like to follow along, subscribe to the channel. I hope you find this useful, and thanks for watching.