Getting Started
FocusFlow is designed to be easily deployed using containers. We provide official Docker images via GitHub Container Registry.
Backend
Docker and Docker Compose
The easiest way to run the FocusFlow backend is using Docker Compose. This automates the setup of both the application server and the required PostgreSQL database.
Prerequisites
- Docker: Install Docker Desktop or Docker Engine.
- Docker Compose: Usually included with Docker Desktop.
Quick Start
-
Create a
docker-compose.ymlfile: Create a new file nameddocker-compose.yml. You can use the following example as a starting point.IMPORTANT! Make sure to replace
JWT_SECRETand passwords with secure values.version: "3.8"services:backend:image: ghcr.io/francesco-gaglione/focusflowcloud:latestrestart: alwaysports:- "8080:8080"environment:# Server Configuration- SERVER_PORT=8080- CORS_ORIGIN=*- APP_ENV=production- RUST_LOG=info# Database Configuration# DATABASE_BASE_URL should be "hostname:port"- DATABASE_BASE_URL=db:5432- POSTGRES_DB=focusflow- POSTGRES_USER=focusflow- POSTGRES_PASSWORD=secure_db_password# Security# Must be a long, random string- JWT_SECRET=change_me_to_a_secure_random_secret# Web Push (VAPID) — used for browser push notifications# Generate with: npx web-push generate-vapid-keys- VAPID_PRIVATE_KEY=your_vapid_private_key# Initial Admin User (Optional)# Uncomment to create an admin user on first run# - ADMIN_USERNAME=admin# - ADMIN_PASSWORD=admin_passworddepends_on:db:condition: service_healthynetworks:- focusflow-netdb:image: postgres:15-alpinerestart: alwaysenvironment:- POSTGRES_USER=focusflow- POSTGRES_PASSWORD=secure_db_password- POSTGRES_DB=focusflowvolumes:- db_data:/var/lib/postgresql/datanetworks:- focusflow-nethealthcheck:test: ["CMD-SHELL", "pg_isready -U focusflow"]interval: 10stimeout: 5sretries: 5volumes:db_data:networks:focusflow-net: -
Authentication: The backend uses JWT for authentication. You MUST provide a
JWT_SECRETenvironment variable. Generate a strong random string (e.g.,openssl rand -base64 32) and set it. -
Web Push (VAPID keys): Push notifications for the browser require a VAPID key pair. Generate it once and store both keys permanently — regenerating them invalidates all existing browser subscriptions.
npx web-push generate-vapid-keysThis outputs:
Public Key: BKq9se... ← safe to expose (not currently required for the native app)Private Key: 1772RK... ← goes in backend env as VAPID_PRIVATE_KEY -
Initial Admin User: Since registration is private, you can seed an initial admin user by setting the
ADMIN_USERNAMEandADMIN_PASSWORDenvironment variables. The user will be created on startup if it doesn't exist. -
Start the services: Run the following command in the same directory as your
docker-compose.yml:docker-compose up -d -
Verify: The backend should now be running at
http://localhost:8080. You can check the logs with:docker-compose logs -f backend
Configuration Reference
All environment variables required for the backend:
| Variable | Description | Example |
|---|---|---|
SERVER_PORT | Port the server listens on | 8080 |
CORS_ORIGIN | Allowed CORS origin | * or https://app.example.com |
JWT_SECRET | Secret key for signing tokens | random_string |
DATABASE_BASE_URL | Hostname and port of the database | db:5432 |
POSTGRES_DB | Database name | focusflow |
POSTGRES_USER | Database user | focusflow |
POSTGRES_PASSWORD | Database password | secure_password |
ADMIN_USERNAME | (Optional) Initial admin username | admin |
ADMIN_PASSWORD | (Optional) Initial admin password | password |
VAPID_PRIVATE_KEY | Private key for Web Push notifications | generated via npx web-push generate-vapid-keys |
OTLP_ENDPOINT | (Optional) OpenTelemetry collector endpoint | http://localhost:4317 |
Observability (Optional)
FocusFlow supports distributed tracing via OpenTelemetry. To enable it, set the OTLP_ENDPOINT environment variable pointing to any OTLP-compatible collector (Jaeger, Grafana Tempo, Honeycomb, Datadog, etc.):
- OTLP_ENDPOINT=http://your-collector:4317
If the variable is not set, tracing is disabled and the backend only emits structured JSON logs to stdout. No collector is required for a basic deployment.
Kubernetes
Kubernetes manifests are provided in the k8s/ directory of the repository.
Prerequisites
- A running Kubernetes cluster (local: minikube, kind; cloud: EKS, GKE, AKS, etc.)
kubectlconfigured to point to your cluster
1. Clone the repository
git clone https://github.com/francesco-gaglione/focus_flow_cloud.git
cd focus_flow_cloud/k8s
2. Configure secrets and settings
Before applying, edit the files to match your environment:
postgres-secret.yaml — base64-encoded database credentials:
echo -n "your_user" | base64
echo -n "your_password" | base64
echo -n "your_db" | base64
focus-flow-cloud-secret.yaml — base64-encoded JWT secret:
echo -n "$(openssl rand -base64 32)" | base64
focus-flow-cloud-config.yaml — application settings (CORS origin, log level, optional OTLP endpoint, admin user).
3. Apply manifests in order
The namespace must be created first. Apply it separately, then apply the rest:
kubectl apply -f namespace.yaml
kubectl apply -f postgres-secret.yaml
kubectl apply -f postgres-config.yaml
kubectl apply -f postgres-volume.yaml
kubectl apply -f postgres.yaml
kubectl apply -f focus-flow-cloud-secret.yaml
kubectl apply -f focus-flow-cloud-config.yaml
kubectl apply -f focus-flow-cloud.yaml
4. Verify the deployment
kubectl get all -n focus-flow-cloud
kubectl logs -l app=focus-flow-cloud -n focus-flow-cloud
kubectl logs -l app=postgres -n focus-flow-cloud
The backend will be available on the NodePort defined in focus-flow-cloud.yaml (default: 30002).
Production notes
- PersistentVolume: The default manifest uses
hostPathstorage, which is only suitable for single-node clusters. - Secrets management: Consider using Sealed Secrets or an external secrets operator.
- Ingress: The default Service type is
NodePort. For production, add an Ingress resource with TLS termination.
Native App
FocusFlow is a native cross-platform application built with Tauri v2 + SvelteKit. It connects to any self-hosted FocusFlow backend.
Downloading
Pre-built binaries are available on the GitHub Releases page:
| Platform | File |
|---|---|
| macOS (Apple Silicon) | .dmg |
| macOS (Intel) | .dmg |
| Linux (Debian/Ubuntu) | .deb |
| Linux (universal) | .AppImage |
| Windows | .exe (NSIS) / .msi |
| Android | .apk |
First Launch
On first launch you will see a Connect to your server screen. Enter the full URL of your backend (e.g. https://api.example.com or http://192.168.1.100:8080) and tap Connect. The app validates the connection before proceeding to the login screen.
The server URL can be changed at any time from Settings → Server.
Android APK Install
Android APKs are distributed outside the Play Store. To install:
- Enable Install from unknown sources in your device security settings.
- Transfer the
.apkfile to your device. - Open and install it.
iOS Install
iOS does not allow installing apps outside the App Store without an Apple Developer account ($99/year). There is no pre-built IPA in the releases.
Option A — Free (7-day expiry):
Requires a Mac with Xcode and Sideloadly.
-
Clone the repo and build the IPA locally:
git clone https://github.com/francesco-gaglione/focus_flow_cloud.gitcd focus_flow_cloud/appbun installbunx tauri ios initbunx tauri ios buildThe IPA will be generated in
app/src-tauri/gen/apple/build/arm64/. -
Connect your iPhone via USB.
-
Open Sideloadly, drag the
.ipainto it, sign with your free Apple ID, and install. -
On the iPhone, go to Settings → General → VPN & Device Management and trust the developer certificate.
Note: Apps signed with a free Apple ID expire after 7 days. Repeat the signing step to renew.
Option B — Paid ($99/year):
With an Apple Developer account you can distribute via TestFlight (no expiry, up to 10,000 testers). See Apple's TestFlight documentation for setup.
Running locally (Development)
Prerequisites: Rust 1.77+, Bun, Tauri prerequisites for your platform.
git clone https://github.com/francesco-gaglione/focus_flow_cloud.git
cd focus_flow_cloud/app
bun install
bun run tauri:dev
On first launch, enter http://localhost:8080 (or wherever your backend is running) as the server URL.
To build a production binary:
bun run tauri:build