Docker
Oyla runs as one container: a Node.js process that serves the web app, the REST API and the live Socket.IO connections, and stores everything in one SQLite file. It needs no database server and no third-party account.
Requirements
Section titled “Requirements”- Docker (or any runtime that builds a Dockerfile).
- A persistent volume for
/data. - If people reach it over the internet: HTTPS in front of the container, from your host or a reverse proxy.
One instance handles a hobby-sized audience, around 100 participants at the same time. Run exactly one container per database: live presence and rate limits are kept in memory, so several replicas would not see each other.
Build and run
Section titled “Build and run”Clone the repository and build the image from its root:
git clone https://github.com/mftnakrsu/oyla.gitcd oyladocker build -t oyla .Run it with a named volume for the data:
docker run -d --name oyla -p 3000:3000 -v oyla-data:/data oylaOpen http://localhost:3000. To check that the server is up from the command line:
curl http://localhost:3000/api/health# {"status":"ok"}To let phones on the same network join, open Oyla by the network address of the machine that runs
it, for example http://192.168.1.20:3000, instead of localhost. The presenter screen shows the
address it was opened on, so that is what participants see and what the QR code points to.
Where the data lives
Section titled “Where the data lives”Everything is in the SQLite database at /data/oyla.db inside the container, so in the
oyla-data volume in the example above. SQLite also keeps oyla.db-wal and oyla.db-shm next to
it while the server runs. The schema is created on first start and migrated automatically when you
update.
Without a volume the data disappears with the container, so always mount one.
To back up, stop the container and copy the volume’s contents, for example:
docker stop oyladocker run --rm -v oyla-data:/data -v "$PWD":/backup busybox \ tar czf /backup/oyla-data.tgz -C /data .docker start oylaAbout the image
Section titled “About the image”- Multi-stage build on Node.js 24: the first stage installs dependencies and builds the web app and the server, the runtime stage keeps only what the server needs.
- Runs as a non-root user, which owns
/data. - Listens on port
3000(PORT) and setsOYLA_DB_PATH=/data/oyla.db. - A
HEALTHCHECKrequests/api/health;docker psshows the container ashealthyonce it answers. - Stops cleanly on
SIGTERM, sodocker stopcloses the database properly.
Configuration
Section titled “Configuration”Pass settings as environment variables with -e. For example, to require a password for creating
presentations:
docker run -d --name oyla -p 3000:3000 -v oyla-data:/data \ -e OYLA_CREATE_PASSWORD='choose-a-long-password' \ oylaAll variables are listed on the configuration page. Oyla
doesn’t read a .env file itself; use --env-file with docker run or env_file in Compose if
you prefer a file.
Docker Compose
Section titled “Docker Compose”The repository includes a docker-compose.yml along these lines:
services: oyla: build: . image: oyla ports: - "3000:3000" volumes: - oyla-data:/data environment: # Behind a reverse proxy, set the number of proxies in front of Oyla. OYLA_TRUST_PROXY: "false" # Uncomment to require a password for creating presentations. # OYLA_CREATE_PASSWORD: change-me restart: unless-stopped
volumes: oyla-data:docker compose up -dUpdate
Section titled “Update”Pull the new code, rebuild and replace the container. The volume keeps the data, and database migrations run when the new container starts.
git pulldocker build -t oyla .docker stop oyla && docker rm oyladocker run -d --name oyla -p 3000:3000 -v oyla-data:/data oylaWith Compose: git pull && docker compose up -d --build.
HTTPS and reverse proxies
Section titled “HTTPS and reverse proxies”Phones on a public network should reach Oyla over HTTPS. Most platforms terminate TLS for you (see Railway). On your own server, put a reverse proxy in front of the container and keep these points in mind:
- The proxy must pass WebSocket upgrades through; the live connection uses Socket.IO on
/socket.io/. - Set
OYLA_TRUST_PROXYto the number of proxies in front of Oyla, usuallytrue(one). Otherwise every visitor seems to come from the proxy’s address and they all share one rate limit. SeeOYLA_TRUST_PROXY. - Publish the port on localhost only, so nobody can bypass the proxy:
-p 127.0.0.1:3000:3000.
With Caddy, which gets certificates and proxies WebSockets automatically:
oyla.example.com { reverse_proxy 127.0.0.1:3000}With nginx, forward the upgrade headers:
server { server_name oyla.example.com; # listen 443 ssl; and your certificate settings
location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}Without Docker
Section titled “Without Docker”You need Node.js 24.
npm installnpm run buildnpm startThe server listens on http://localhost:3000 and stores data in ./data/oyla.db. Use a process
manager such as systemd to keep it running, and set the same environment variables as above.