---
title: Deploying
description: Run Sluurp on a server, with HTTPS in front, backups and upgrades.
section: Operations
order: 3
---

# Deploying

<p class="lead">In production, a Sluurp app is one binary, one app folder and one data folder on a machine you control. No database server, no build step.</p>

## In one click

Start from [SluurpHQ/starter](https://github.com/SluurpHQ/starter), a template with a small app and everything a host needs. Each option below keeps your data on a disk of its own, so it survives a redeploy. For commercial use, set your licence as `SLUURP_LICENSE`.

- **Render**: the template's Deploy to Render button, with a 1 GB disk.
- **Railway**: a project from the template, plus a volume at `/data`.
- **Fly.io**: `fly launch`, a volume named `data`, then `fly deploy`.
- **DigitalOcean, Hetzner or any Ubuntu or Debian server**: paste [cloud-init.yaml](https://github.com/SluurpHQ/releases/blob/main/deploy/cloud-init.yaml) as the server's user data when you create it. Fill in your domain, the first admin and your licence first. On DigitalOcean that's Create Droplet, Advanced options; on Hetzner Cloud it's the Cloud config field. Sluurp runs as a service behind Caddy, with HTTPS.

## Docker

```sh title="Terminal"
docker run -d -p 8090:8090 -v sluurp-data:/data -v "$PWD/app:/app" ghcr.io/sluurphq/sluurp
```

The image serves the app in `/app` and keeps its data in `/data`. To ship your app inside it:

```dockerfile title="Dockerfile"
FROM ghcr.io/sluurphq/sluurp
COPY --chown=sluurp app /app
```

## On the server

Install the binary, copy the app, and create an admin:

```sh title="Terminal"
curl -fsSL https://raw.githubusercontent.com/SluurpHQ/releases/main/install.sh | sh
sluurp --dir /srv/sluurp/data superuser you@example.com a-long-password
sluurp --dir /srv/sluurp/data serve --public /srv/sluurp/app --no-hot-reload
```

- `--dir` is the data folder: one SQLite file per project. It's the only folder you need to back up.
- `--no-hot-reload` turns off file watching, which production doesn't need.
- `--public` can also be the app's [repository URL](/docs/getting-started): it's cloned on first start and pulled on each restart.

Sluurp listens on `127.0.0.1:8090` (local only). Use `--addr 0.0.0.0:8090` to accept outside connections, or better, put a reverse proxy in front.

## HTTPS

Sluurp serves plain HTTP; a reverse proxy adds HTTPS. [Caddy](https://caddyserver.com) obtains and renews certificates automatically:

```text title="Caddyfile"
school.example.com {
  reverse_proxy 127.0.0.1:8090
}
```

Realtime, sync and cursors use WebSockets, which Caddy proxies out of the box. With nginx, forward the `Upgrade` and `Connection` headers.

## Running as a service

A systemd unit that starts Sluurp at boot and restarts it on failure:

```ini title="/etc/systemd/system/sluurp.service"
[Unit]
Description=Sluurp
After=network.target

[Service]
ExecStart=/home/sluurp/.sluurp/bin/sluurp --dir /srv/sluurp/data serve --public /srv/sluurp/app --no-hot-reload
User=sluurp
Restart=always
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
```

```sh title="Terminal"
sudo systemctl enable --now sluurp
```

## Storage

Everything Sluurp keeps is in one folder, the data folder: `--dir`, or `/data` in the Docker image.

| | |
|---|---|
| `_system.db`, `default.db`, one `.db` per project | The databases: your records, users, settings, pages, and the files server code keeps. |
| `storage/` | Files people upload, a folder per project. Usually the biggest part. |
| `snapshots/` | Backups, when you make them here. |
| `logs/` | Request logs older than a week, one gzipped file a day. |
| `bundles/` | Production bundles of your app, made at start. They can be made again. |

Backups in the admin copy the databases only. Back up `storage/` (or the `SLUURP_FILES` folder) as well, with your usual tools, or keep files in a bucket, below.

### Files on a disk of their own

Uploads often outgrow the databases many times over. To keep them on another disk or volume, set `SLUURP_FILES` to a folder there:

```sh title="Terminal"
SLUURP_FILES=/mnt/files sluurp --dir /srv/sluurp/data serve --public /srv/sluurp/app
```

With Docker, mount a volume for them. Either mount it where the files already go:

```sh title="Terminal"
docker run -d -p 8090:8090 \
  -v sluurp-data:/data \
  -v /mnt/big-disk/sluurp:/data/storage \
  -v "$PWD/app:/app" ghcr.io/sluurphq/sluurp
```

or mount it anywhere and say where:

```sh title="Terminal"
docker run -d -p 8090:8090 -e SLUURP_FILES=/files \
  -v sluurp-data:/data -v sluurp-files:/files \
  -v "$PWD/app:/app" ghcr.io/sluurphq/sluurp
```

When you move existing files, stop Sluurp, copy the `storage/` folder to the new place, then start it with the new setting.

The admin shows where the files are, and how much room is left on that disk, under Settings, Storage. The same screen sets how much the project, and each person, may upload; see [Files](/docs/files#quotas).

### Files in a bucket

With more than one server, or to keep files off the server, add an S3-compatible bucket: AWS S3, Cloudflare R2, Backblaze B2 or MinIO. Each upload is copied to the bucket, and a server that lacks a file fetches it from there. See [Files](/docs/files#storage) for the settings.

## Many connections

One Sluurp handles tens of thousands of open connections: pages, API calls, live queries over WebSockets. Each one costs a few kilobytes and no thread. The operating system has limits of its own, though, and on Linux the first one is 1,024 open files per process. Every connection is an open file.

Sluurp raises its own limit as far as the system lets it when it starts. The `LimitNOFILE` line in the unit above lets it go that far. The server install script also sets these, and you can set them yourself on any Linux server:

```ini title="/etc/sysctl.d/90-sluurp.conf"
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
fs.file-max = 2097152
```

```sh title="Terminal"
sudo sysctl --system
```

They make room for a burst of new connections, give requests your functions make to other services all the local ports, and reuse closed sockets sooner. With Docker, pass `--ulimit nofile=1048576:1048576` to `docker run`.

If Caddy or nginx sits in front, it holds a connection to each client and one to Sluurp, so give it the same limit.

## Backups

`sluurp backup` writes a dated snapshot of every project to `snapshots/` in the data folder. **Backups** in the admin UI schedules them and sets how many to keep. Copy snapshots off the machine too: a backup on the same disk won't survive that disk.

## Upgrades

Replace the binary and restart. On startup Sluurp upgrades its own tables and runs any pending app [migrations](/docs/migrations). Back up first; `sluurp migrate APP --plan` shows what will run.

## No server at all

A site that doesn't need the API, sync or server functions (a landing page, docs) can be exported as static files with [`sluurp static`](/docs/static-sites) and hosted on GitHub Pages or any file host.
