Self-hosting Parletto

What Parletto needs to run on your own server, and how to run it with Docker.

Parletto is a Ruby on Rails app with a React front end and SQLite for storage, so one Docker container on one server is enough to run it. Uploaded files are stored on local disk by default, with Cloudflare R2 as an option.

The source is opening shortly under the MIT license. The steps below run from a checkout of it (the parletto-core directory). If you would rather not run Parletto yourself, the managed service at app.parletto.com is the same software.

What you need

  • A server with Docker. You don’t need Ruby or Node on the server.
  • No Redis or external database. SQLite holds the app data, cache, jobs and WebSockets.
  • A Docker volume for /rails/storage, where Parletto keeps everything it stores.
  • For HTTPS on a real server: a DNS name pointing at the server, or a reverse proxy that terminates TLS.

Quick start with Docker Compose

From the source checkout, copy .env.example to .env, add a SECRET_KEY_BASE, and build and start the container:

cd parletto-core
cp .env.example .env
echo "SECRET_KEY_BASE=$(openssl rand -hex 64)" >> .env
docker compose up -d --build

SECRET_KEY_BASE signs sessions and cookies. Keep it stable, because changing it signs everyone out.

Open http://localhost:3000 and sign up. A fresh database has no accounts, so the first person to sign up creates the first organization. To use a different port on the host, set PARLETTO_PORT (it defaults to 3000).

Once the source is public, you won’t need a checkout: a prebuilt linux/amd64 image is published as ghcr.io/grafficmedia/parletto-core:latest, and docker-compose.yml uses it, so docker compose up -d (without --build) next to the compose file and your .env is enough. On an arm64 host, keep building from source.

The image runs Rails behind Thruster, which serves HTTP on port 80 inside the container. When the container starts, bin/docker-entrypoint runs bin/rails db:prepare, which creates or migrates the SQLite databases. The Compose file also sets SOLID_QUEUE_IN_PUMA=true, so background jobs run inside the web process, and health-checks the container on /up, which returns 200 once the app has booted.

Keep the storage volume

Everything Parletto stores lives in /rails/storage inside the container:

  • the SQLite databases (production.sqlite3, plus the cache, queue and cable databases),
  • uploaded avatars, organization icons and attachments, unless R2 is configured,
  • Thruster’s TLS certificates.

With Docker Compose this is the parletto_storage volume, so your data survives docker compose down and image rebuilds. If you run the container by hand, mount a named volume there (-v parletto_storage:/rails/storage), or your data is lost when the container is replaced.

HTTP or HTTPS

Production forces HTTPS, so sign-in redirects to https:// even when you start on plain HTTP. .env.example sets DISABLE_SSL=true so the app works over plain HTTP on localhost. Use it only for a trial on http://localhost or behind a proxy that serves the app over plain HTTP.

On a real server, delete that line and serve HTTPS. Either set TLS_DOMAIN so Thruster gets a Let’s Encrypt certificate (this needs ports 80 and 443 published, so edit the ports in docker-compose.yml), or leave TLS_DOMAIN unset and let a reverse proxy in front of the container terminate TLS.

Environment variables

Variable Required What it does
RAILS_MASTER_KEY With credentials Decrypts config/credentials/production.yml.enc. Use the contents of production.key.
SECRET_KEY_BASE Without credentials Signs sessions and cookies. It overrides secret_key_base in the credentials. Keep it stable, because changing it signs everyone out.
DISABLE_SSL No Set to true to stop forcing HTTPS. Use it only for a trial on http://localhost or behind a proxy that serves the app over plain HTTP.
TLS_DOMAIN Recommended Domain(s) Thruster gets a Let’s Encrypt certificate for. Leave it unset if a reverse proxy in front of the container terminates TLS.
SOLID_QUEUE_IN_PUMA Recommended Set to true to run background jobs (emails and the like) inside the web process. Otherwise run bin/jobs in a second container with the same volume.
ACTION_MAILER_HOST For email Host used in links in invitation and password-reset emails. Defaults to example.com.
RAILS_LOG_LEVEL No Defaults to info.
WEB_CONCURRENCY / RAILS_MAX_THREADS / JOB_CONCURRENCY No Puma workers, threads per worker, and Solid Queue processes.

Secrets such as the R2 and SMTP settings live in an encrypted Rails credentials file (config/credentials/production.yml.enc), not in environment variables. Rails decrypts it with RAILS_MASTER_KEY. Runtime settings such as ACTION_MAILER_HOST go in .env or your platform’s environment. Never commit .env or the key.

Uploads: local disk or Cloudflare R2

R2 is optional. Parletto stores uploads in R2 when the credentials contain r2.bucket, and on local disk in /rails/storage when they don’t. Everything works either way.

On local disk, the app serves uploaded files (avatars, organization icons, attachments) itself at /uploads/..., and only to signed-in members of the file’s organization. With placeholder r2 values, uploads fail, so leave the r2 section out if you don’t have a bucket.

To store uploads in R2, create an R2 bucket and API token and put the real values in the r2 section of the credentials. SMTP settings for invitation and password-reset emails go in the same file. Then mount production.yml.enc into the container (with Compose, uncomment the production.yml.enc line under volumes in docker-compose.yml) and set RAILS_MASTER_KEY in .env. The README in the source walks through creating the credentials file and serving uploads from a CDN.

Upgrading

Take a backup of the storage volume first. Migrations run automatically when the container starts, because the entrypoint runs bin/rails db:prepare.

With Docker Compose, from source

git pull origin main
docker compose up -d --build

With Docker Compose, prebuilt image

Once the image is public, pull the new image instead of rebuilding. Images are published only for releases: latest is the newest release, and each release is also tagged with its version, such as v0.1.0.

docker compose pull && docker compose up -d

With docker run

git pull origin main
docker build -t parletto .
docker stop parletto && docker rm parletto
docker run -d --name parletto ...   # same flags, volume and credentials as before