Running Parletto in production
What comes after the quick start on your own server: TLS, email, file uploads, backups, upgrades and troubleshooting.
Start with Self-hosting Parletto, which covers the image, the storage volume, the environment variables and a first run. This page covers what comes after that on a real server: TLS, email, file uploads, backups, upgrades and troubleshooting. It follows the deployment guide in the Parletto source, which is opening shortly under the MIT license.
The commands below use the container name parletto, as in the
docker run steps in the source’s README.
Server requirements
- Minimum: 1 GB RAM, 1 CPU core, 10 GB disk.
- Recommended: 2 GB RAM, 2 CPU cores, 20 GB disk.
- Any Linux host that runs Docker. Ports 80 and 443 must be reachable if Thruster handles TLS.
Redis, PostgreSQL and a separate web server are not needed. SQLite holds everything: app data, cache (Solid Cache), the job queue (Solid Queue) and WebSocket pub/sub (Solid Cable).
How the container runs
-
On every start,
bin/docker-entrypointrunsbin/rails db:prepare. On an empty volume this creates the four databases (production,production_cache,production_queue,production_cable). On later starts it applies any pending migrations. - A fresh production database has no accounts. Sign up to create the first one.
-
The server is Puma behind Thruster. Thruster listens on port 80, and on port 443 too when
TLS_DOMAINis set. -
Background jobs run inside Puma when
SOLID_QUEUE_IN_PUMA=true. To run them separately, start a second container from the same image and volume with the commandbin/jobs. /upis the health check. It returns 200 when the app has booted.-
Secrets (SMTP, R2, the asset cookie) come from
config/credentials/production.yml.enc, decrypted withRAILS_MASTER_KEY.config/credentials.example.ymlin the source lists every key. Without credentials, the app boots with onlySECRET_KEY_BASEset: email then does not work, and uploads are stored on local disk.
TLS
Production sets config.force_ssl and
config.assume_ssl, so cookies are Secure and
HSTS is sent. Pick one of these:
- Thruster: set
TLS_DOMAIN=chat.example.comand publish ports 80 and 443. Certificates come from Let’s Encrypt and are stored in/rails/storage/thruster, inside the volume. - Your own reverse proxy (Caddy, nginx, Traefik, a load balancer): leave
TLS_DOMAINunset, terminate TLS in the proxy and forward to the container’s port 80. The proxy must sendX-Forwarded-Proto: httpsand pass WebSocket upgrades for/cable.
If you use Cloudflare in front of the server, set the SSL/TLS mode to “Full”.
DISABLE_SSL=true turns off force_ssl and
assume_ssl. The Compose quick start sets it so you can try Parletto on
http://localhost:3000. Remove it before you expose the server, unless a proxy in front of it
deliberately serves plain HTTP.
Docker Compose on a server
-
In
.env, deleteDISABLE_SSL=trueand setTLS_DOMAINandACTION_MAILER_HOSTto your domain. -
In
docker-compose.yml, publish"80:80"and"443:443"instead of3000:80, or keep the port and put your own TLS proxy in front. -
For SMTP and R2, create production credentials (the README in the source walks through it),
uncomment the
production.yml.encmount, and setRAILS_MASTER_KEYin.env.
Email (SMTP)
Email is used for organization invitations and password resets. The app runs without it, but those emails will fail to send.
SMTP settings are read from the production credentials. Add an smtp
section to the production.yml you write when creating the credentials, and
encrypt it again:
smtp:
user_name: apikey # SendGrid uses the literal string "apikey"
password: YOUR_SMTP_PASSWORD
address: smtp.sendgrid.net # any SMTP server works
port: 587
authentication: plain
from_email: team@your-domain.com
Then restart the container with the new production.yml.enc, and set
ACTION_MAILER_HOST to your domain so that links in emails point to it.
File uploads: local disk or Cloudflare R2
Parletto uses the r2 storage service when the credentials contain
r2.bucket, and local disk (/rails/storage, inside
the volume) otherwise.
Local disk (default). Nothing to configure. Avatars, organization icons and message
attachments are saved in the volume and served by the app at
/uploads/<key>. Before sending a file, the app checks that the
signed-in user is a member of the file’s organization (for avatars: the user themselves or someone
sharing an organization). Everyone else gets a 404. Back up the volume to keep uploads.
Cloudflare R2 and the asset Worker (optional). Use this to keep uploads off the
server and serve them from a CDN. It needs an R2 bucket and credentials in the
r2 section, an asset_cookie section with a
cdn_host, and the Worker in workers/asset-auth
deployed with Wrangler. The app does not hand out direct R2 URLs: it builds
https://<cdn_host>/<key> URLs, and the Worker serves them after
checking a signed cookie the app issues. Your app and assets hosts must share a parent domain so the
cookie reaches both. The deployment guide and docs/r2-setup.md in the
source cover the steps.
Backups
All data is in the storage volume. To make a consistent copy of the main database while the app is running, use SQLite’s backup command:
docker exec parletto sqlite3 /rails/storage/production.sqlite3 ".backup '/rails/storage/backup-$(date +%Y%m%d).sqlite3'"
docker cp parletto:/rails/storage/backup-$(date +%Y%m%d).sqlite3 ./backups/
The cache, queue and cable databases can be recreated, so of the databases only
production.sqlite3 needs a backup. Uploads stored on local disk are files
in the same volume (/rails/storage, excluding the
*.sqlite3 files), so copy those too. If you store uploads in R2, back them
up separately. Keep backups off the server.
Upgrades
Take a backup first. Migrations run automatically when the container starts.
With Docker Compose
docker compose pull && docker compose up -d
If you build from source, git pull and run
docker compose up -d --build instead.
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 Troubleshooting
Start with docker logs parletto.
- Container exits on boot
-
Missing secret_key_baseorActiveSupport::MessageEncryptor::InvalidMessage: with Compose and no credentials,SECRET_KEY_BASEis missing from.env. With credentials,RAILS_MASTER_KEYdoes not match the mountedproduction.yml.enc, or the file is not mounted. - Signed out immediately or sign-in does not stick
-
Cookies are
Secure, so you need HTTPS everywhere exceptlocalhost, unlessDISABLE_SSL=true. Check that TLS is set up and that your proxy sendsX-Forwarded-Proto: https. - Real-time updates not arriving
-
Solid Cable stores messages in
production_cable.sqlite3, so check that the volume is mounted and writable. A reverse proxy in front of the app must pass WebSocket upgrades for/cable. - Emails not sending
-
Check the
smtpcredentials andACTION_MAILER_HOST, and that background jobs are running (SOLID_QUEUE_IN_PUMA=trueor abin/jobscontainer). To test from a console:docker exec -it parletto bin/rails runner \ 'ActionMailer::Base.mail(from: "test@example.com", to: "you@example.com", subject: "Test", body: "Test").deliver_now' - Uploads fail or images do not show
-
For a 404 from
/uploads/..., check that you are signed in as a member of the file’s organization. For a 401 or 403 from the assets host, check that the Worker’sCOOKIE_SECRETmatchesasset_cookie.secret, that the R2 bindings are right, and that theparletto_assetscookie is set on your parent domain. - Database locked errors
-
Only one container should write to a given volume, apart from an optional
bin/jobscontainer. Check free disk space andbin/rails db:migrate:status.
Security checklist
- Never commit
config/credentials/*.keyor.envfiles. - Serve the app over HTTPS only.
-
Keep
production.key(RAILS_MASTER_KEY) private and backed up, and keep it out of the repo directory when you build images. - Back up
production.sqlite3regularly and store the backups off the server. - Rebuild regularly to pick up Ruby, gem and npm security updates.