
This article provides a comprehensive, step-by-step guide to deploy self-hosted AppFlowy on Ubuntu VPS (20.04 or later). This walkthrough assumes you have sudo privileges on your VPS and a basic understanding of the Linux shell 🖥️.
What is AppFlowy?
AppFlowy is an open-source, AI-powered collaborative workspace designed for note-taking, project and task management, wikis, and much more—often described as a privacy-focused, customizable alternative to Notion.
Key Features & Highlights
- Complete Data Control & Privacy
- AppFlowy emphasizes data privacy by enabling full offline use and self-hosting, giving users full control over their data without vendor lock-in.
- Cross-Platform Native Experience
- Built with Flutter (for UI) and Rust (for backend), AppFlowy offers fast, responsive, native apps across Windows, macOS, Linux, iOS, Android—and can even run completely offline.
- Rich Workspace Customization
- It supports a variety of content types—notes, code blocks, math equations, attachments, kanban boards, tables, calendars, wikis, and more—with flexible views, templates, and customization options.
- AI Integration
- Built-in AI features let you write smarter, generate ideas, get answers, and enhance productivity. You can use advanced models like GPT‑4, Claude 3 Sonnet, Llama 3, and Mistral 7B, either in the cloud or locally for maximum privacy.
- Strong Community & Open‑Source Foundations
- Licensed under AGPL-v3, AppFlowy is community-driven. It benefits from active development—tens of thousands of GitHub stars, hundreds of contributors, and a growing ecosystem of addons, templates, and themes.
- Self‑Hosting & Deployment Flexibility
- Users have full flexibility: deploy on their own servers via AppFlowy Cloud, Docker, or services like Supabase, ensuring both privacy and ease of deployment.
Who Is AppFlowy For?
- Users seeking a privacy-first productivity tool with full offline and self-hosted capabilities.
- Those who want a Notion-like experience but with open-source transparency and customization flexibility.
- Developers or teams who want to extend the app through plugins, templates, and custom themes.
- Users interested in leveraging AI features in their note-taking and task workflows without sacrificing control over personal or organizational data.
Summary Table
| Feature | Benefit |
|---|---|
| Open Source | Transparent, community-driven development |
| Self-Hostable | Full control of data and setup configuration |
| AI-Powered | Content generation, summarization, productivity |
| Cross-Platform | Native apps on desktop and mobile |
| Customizable | Multiple content types, views, themes, and templates |
| Privacy-Focused | Local AI model support & offline functionality |
In summary, AppFlowy is a powerful, flexible, and secure productivity platform that combines the best of Notion-style organization with the freedom, transparency, and privacy of open-source software. Whether you’re working solo or building tools for your team, AppFlowy gives you control and room to evolve.
Compare Ubuntu VPS Plans
🚀 Deploy Self-Hosted AppFlowy on Ubuntu VPS
To deploy self-hosted AppFlowy on Ubuntu VPS, follow the steps below:
-
System Preparation
Login to your VPS via SSH and execute the following commands:
sudo apt update && sudo apt upgrade -y sudo apt install git curl unzip build-essential libssl-dev pkg-config libclang-dev cmake -y
✅ These packages are essential for building AppFlowy and managing dependencies.
-
Install Rust
AppFlowy is built using Rust and Flutter. First, install Rust:
curl https://sh.rustup.rs -sSf | sh source $HOME/.cargo/env rustup default stable
Verify installation:
rustc --version
-
Install Flutter
Clone the Flutter repository and add it to your path:
git clone https://github.com/flutter/flutter.git -b stable echo 'export PATH="$PATH:$HOME/flutter/bin"' >> ~/.bashrc source ~/.bashrc
Install Flutter dependencies:
flutter doctor
⚠️ You may see some warnings (e.g., Android toolchain) — those can be ignored unless you plan to build mobile apps.
-
Clone AppFlowy Repository
git clone https://github.com/AppFlowy-IO/AppFlowy.git cd AppFlowy
-
Build AppFlowy Backend (Rust)
cd appflowy_backend cargo build --release
This builds the core backend binary you’ll be using.
-
Setup Backend Configuration
Create a
.envfile in theappflowy_backenddirectory:cp .env.example .env nano .env
Configure database type (default is
sqlite). You can changeDATABASE_URLif you want to use PostgreSQL.Example:
DATABASE_TYPE=sqlite DATABASE_URL=appflowy.db APP_PORT=8080
-
Run the Backend Server
./target/release/appflowy_backend
This will start the backend API on port
8080. You can daemonize this usingsystemdortmuxlater. -
Build the Frontend (Flutter Web)
In a separate terminal:
cd AppFlowy/frontend flutter config --enable-web flutter pub get flutter build web
The static web files will be located at:
build/web/
-
Serve the Frontend
You can use a web server like Nginx to serve the frontend.
Install Nginx:
sudo apt install nginx -y
Configure Nginx:
sudo nano /etc/nginx/sites-available/appflowy
Paste the following:
server { listen 80; server_name your_domain_or_ip; root /path/to/AppFlowy/frontend/build/web; index index.html; location / { try_files $uri $uri/ =404; } location /api/ { proxy_pass http://localhost:8080/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }Enable site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/appflowy /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl restart nginx
-
Setup systemd Service
sudo nano /etc/systemd/system/appflowy.service
Paste:
[Unit] Description=AppFlowy Backend After=network.target [Service] ExecStart=/home/youruser/AppFlowy/appflowy_backend/target/release/appflowy_backend WorkingDirectory=/home/youruser/AppFlowy/appflowy_backend Restart=always User=youruser [Install] WantedBy=multi-user.target
Enable & start the service:
sudo systemctl daemon-reexec sudo systemctl enable appflowy sudo systemctl start appflowy
-
Enable HTTPS with Let’s Encrypt
Let’s now secure your AppFlowy instance with HTTPS using Let’s Encrypt + Certbot. This step ensures encrypted traffic and higher trust for your self-hosted application.
Prerequisites:
- You must own a domain name (e.g.,
yourdomain.com). - The domain must point to your VPS IP via DNS A/AAAA record.
- Port 80 and 443 must be open in your firewall.
Install Certbot (Nginx plugin):
sudo apt install certbot python3-certbot-nginx -y
Obtain and Configure HTTPS Certificate:
sudo certbot --nginx -d yourdomain.com
- Follow the interactive prompts:
- It will find your Nginx config.
- Choose the redirect HTTP to HTTPS option when prompted.
Auto-Renewal Test:
sudo certbot renew --dry-run
If successful, you’re all set. Certbot will renew automatically via a cron job.
Check Everything
Now visit:
https://yourdomain.com✅Ensure:
- You get a valid SSL certificate.
- The site redirects from HTTP to HTTPS.
- The frontend works and communicates with the backend via
/api/.
Troubleshooting Tips
Problem Resolution Port 80 or 443 not open Run sudo ufw allow 'Nginx Full'Domain doesn’t resolve Check DNS A/AAAA record with dig yourdomain.com +shortSSL cert fails Ensure no typos in domain and that Nginx is running properly Backend 502 errors Check systemctl status appflowyandjournalctl -xelogs - You must own a domain name (e.g.,
-
Backup Strategy for Self-Hosted AppFlowy
Let’s move on to setting up backups for your AppFlowy instance — critical for disaster recovery, upgrades, or server migrations.
AppFlowy stores data in:- SQLite database (default) OR PostgreSQL, depending on your config
- Binary builds and frontend assets (can be recompiled)
- Configuration files like
.env, Nginx, systemd
So, we focus on backing up:
- AppFlowy data (
appflowy.dbor PostgreSQL dump) - Configuration files
- Optionally, full directory structure for cold backup
Backup Script:
backup_appflowy.sh#!/bin/bash # === CONFIGURATION === BACKUP_DIR="$HOME/appflowy_backups" APPFLOWY_DIR="$HOME/AppFlowy" TIMESTAMP=$(date +"%Y-%m-%d_%H-%M") ARCHIVE_NAME="appflowy_backup_$TIMESTAMP.tar.gz" # === SQLITE PATH (default case) === SQLITE_FILE="$APPFLOWY_DIR/appflowy_backend/appflowy.db" # === Create backup directory if not exists === mkdir -p "$BACKUP_DIR" echo "Creating backup..." tar -czvf "$BACKUP_DIR/$ARCHIVE_NAME" \ "$SQLITE_FILE" \ "$APPFLOWY_DIR/appflowy_backend/.env" \ "$APPFLOWY_DIR/frontend/build/web" \ "/etc/nginx/sites-available/appflowy" \ "/etc/systemd/system/appflowy.service" echo "Backup saved at: $BACKUP_DIR/$ARCHIVE_NAME"Instructions
- Save:
nano backup_appflowy.sh
Paste the code above.
- Make it executable:
chmod +x backup_appflowy.sh
- Run manually:
./backup_appflowy.sh
Schedule Automatic Backups (Daily via
cron)crontab -e
Add:
0 2 * * * /home/youruser/backup_appflowy.sh >> /home/youruser/backup_log.txt 2>&1
This runs daily at 2:00 AM.
☁️ Optional: Push Backup to Cloud (e.g., S3)
You can add something like:
aws s3 cp "$BACKUP_DIR/$ARCHIVE_NAME" s3://your-bucket-name/path/
Make sure to configure
awscliwith credentials beforehand. -
Restore Script for AppFlowy:
restore_appflowy.shLet’s now cover how to restore a backup of your AppFlowy installation. This is essential for:
- Recovering from system failure
- Migrating to a new VPS
- Reverting to a stable version
This script assumes:
- You’re restoring from a
.tar.gzfile created bybackup_appflowy.sh - You have a fresh or existing AppFlowy install directory (we’ll overwrite parts of it)
Script:
restore_appflowy.sh#!/bin/bash # === CONFIGURATION === BACKUP_FILE="$1" APPFLOWY_DIR="$HOME/AppFlowy" SQLITE_FILE="$APPFLOWY_DIR/appflowy_backend/appflowy.db" if [[ -z "$BACKUP_FILE" || ! -f "$BACKUP_FILE" ]]; then echo "Please provide a valid backup file (e.g., ./restore_appflowy.sh backup.tar.gz)" exit 1 fi echo "Stopping services..." sudo systemctl stop appflowy sudo systemctl stop nginx echo "Restoring backup from: $BACKUP_FILE" tar -xzvf "$BACKUP_FILE" -C / echo "Fixing file permissions..." chown "$USER":"$USER" "$SQLITE_FILE" chmod 600 "$SQLITE_FILE" echo "Restarting services..." sudo systemctl daemon-reexec sudo systemctl start appflowy sudo systemctl start nginx echo "Restore completed. Check your AppFlowy instance."
Usage Instructions
- Save:
nano restore_appflowy.sh
- Paste the code above and make it executable:
chmod +x restore_appflowy.sh
- Run it like this:
./restore_appflowy.sh /home/youruser/appflowy_backups/appflowy_backup_2025-06-25_02-00.tar.gz
Post-Restore Checklist
- Visit
https://yourdomain.com - Confirm all pages and blocks are intact
- Check backend logs for any migration errors:
journalctl -u appflowy --no-pager
Optional Enhancements
- 🧳 Backup and restore PostgreSQL instead of SQLite (if configured)
- 💽 Store backups remotely with
rclone,scp, orrsync - 📁 Use LVM or ZFS snapshots for full-disk recovery (advanced)
-
Remote Monitoring Setup for AppFlowy
Let’s now enhance the AppFlowy server with remote monitoring and alerting, so you’ll be notified of outages, backend failures, or resource exhaustion — all vital for production readiness.
We’ll cover:- System monitoring with Netdata
- Uptime monitoring with Uptime Kuma
- Optional: push notifications via Telegram, Email, or Discord
Option A: System Health Monitoring with Netdata
Netdata Highlights:
- Live CPU, memory, disk, and network graphs
- Tracks service processes (e.g.,
appflowy,nginx) - Web dashboard (localhost or remote)
Install Netdata:
bash <(curl -Ss https://my-netdata.io/kickstart.sh)
Visit your VPS at:
http://your-server-ip:19999
Secure it by setting up Nginx reverse proxy + basic auth if desired.
Option B: Uptime Kuma for Endpoint Monitoring
What it does:
- Periodically pings your frontend (
/) and backend API (/api/health) - Alerts on downtime via email, Discord, Telegram, etc.
- Simple Docker-based deployment
Install Uptime Kuma with Docker:
sudo apt install docker.io docker-compose -y mkdir -p $HOME/uptime-kuma cd $HOME/uptime-kuma nano docker-compose.yml
Paste:
version: "3" services: uptime-kuma: image: louislam/uptime-kuma container_name: uptime-kuma restart: always ports: - "3001:3001" volumes: - ./data:/app/dataStart it:
docker-compose up -d
Visit:
http://your-server-ip:3001
Set up monitors:
- Frontend:
https://yourdomain.com - Backend:
https://yourdomain.com/api/health(if your AppFlowy backend exposes health check)
Optional: Enable Alerts (via Uptime Kuma)
- Go to Settings → Notification Settings
- Add channels:
- Telegram Bot
- Discord Webhook
- Email (SMTP)
- Tie alerts to individual monitors
Monitoring
systemdServices (Optional)Add service watchdog:
sudo systemctl edit appflowy
Add in
[Service]block:Restart=always RestartSec=5
Then reload:
sudo systemctl daemon-reexec sudo systemctl restart appflowy
📌 Summary of What You Now Have
Monitoring Tool Purpose Access URL Netdata Resource & process monitoring http://your-ip:19999Uptime Kuma Uptime checks & alerts http://your-ip:3001systemd Automatic restarts on crash systemctl status appflowy -
Performance Tuning AppFlowy on Ubuntu VPS
We’ll focus on optimizing:
- Rust backend performance
- Flutter web serving via Nginx
- System-level tuning (RAM, swap, ulimits)
- Database tuning (if using PostgreSQL)
Performance Tuning AppFlowy on Ubuntu VPS steps:
-
Rust Backend Optimizations
Compile with Optimizations (Already Done)
AppFlowy’s backend is compiled with:
cargo build --release
Ensure you always run the release binary (
target/release/appflowy_backend).Systemd Restart Strategy
Ensure backend restarts quickly after failures:
Restart=always RestartSec=3
Use Thread Pool (Optional)
Rust async handling benefits from tuning thread pools. If AppFlowy exposes such configs in the future, you’d set:
RUST_BACKTRACE=0 RAYON_NUM_THREADS=4
-
Nginx Tuning for Flutter Web
Edit your Nginx site config:
sudo nano /etc/nginx/sites-available/appflowy
Apply performance headers and static caching:
location / { try_files $uri $uri/ =404; add_header Cache-Control "public, max-age=604800, immutable"; } location ~* \.(js|css|png|jpg|jpeg|svg|woff2)$ { add_header Cache-Control "public, max-age=31536000, immutable"; }Increase Worker Limits
In
/etc/nginx/nginx.conf:worker_processes auto; events { worker_connections 4096; multi_accept on; }Then reload:
sudo nginx -t && sudo systemctl reload nginx
-
System-Level Performance Tuning
Boost Ulimits (file handles, processes):
In
/etc/security/limits.conf:* soft nofile 65535 * hard nofile 65535
Also, in
/etc/systemd/system.conf:DefaultLimitNOFILE=65535
Then apply:
sudo systemctl daemon-reexec
Manage Memory & Swap:
Check RAM usage:
free -h
Enable swap if you have < 2GB RAM:
sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab
-
Database Tuning (if using PostgreSQL)
If you switched from SQLite to PostgreSQL, optimize with:
Use
pgtunepresets:sudo nano /etc/postgresql/14/main/postgresql.conf
Tune parameters like:
shared_buffers = 512MB work_mem = 8MB maintenance_work_mem = 64MB effective_cache_size = 1GB max_connections = 100
Restart PostgreSQL:
sudo systemctl restart postgresql
Validation: Monitor Performance
Use
htop,iotop,netstat, andjournalctlfor real-time analysis:htop iotop sudo journalctl -u appflowy -f
You can also monitor request load in Nginx:
sudo tail -f /var/log/nginx/access.log
-
Run AppFlowy Behind a WireGuard VPN (Ubuntu VPS)
Running AppFlowy behind a WireGuard VPN is a secure way to expose the service only to trusted clients (e.g., your laptop or office network), eliminating the need to open ports to the public internet.
We’ll configure:- WireGuard server on the VPS
- A peer (your client device)
- AppFlowy + Nginx to bind only to VPN interface
- Security validation and firewall isolation
To Run AppFlowy behind a WireGuard VPN, follow the steps below:
-
Install WireGuard on the VPS
sudo apt update sudo apt install wireguard -y
Generate keys:
wg genkey | tee privatekey | wg pubkey > publickey
Save:
cat privatekey cat publickey
-
Configure WireGuard Server (
/etc/wireguard/wg0.conf)sudo nano /etc/wireguard/wg0.conf
Paste (replace
YOUR_SERVER_PUBLIC_IPand keys):[Interface] Address = 10.0.0.1/24 ListenPort = 51820 PrivateKey = [Peer] # Client (will be defined after client setup) PublicKey = AllowedIPs = 10.0.0.2/32
Enable IP forwarding:
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p
Start WireGuard:
sudo systemctl start wg-quick@wg0 sudo systemctl enable wg-quick@wg0
-
Configure Client Peer (Laptop or Remote Machine)
Install WireGuard, then create client config:
[Interface] PrivateKey = Address = 10.0.0.2/24 DNS = 1.1.1.1 [Peer] PublicKey = Endpoint = YOUR_SERVER_PUBLIC_IP:51820 AllowedIPs = 10.0.0.1/32 PersistentKeepalive = 25
Start VPN tunnel:
sudo wg-quick up wg0
Verify connection from client:
ping 10.0.0.1
-
Lock Down AppFlowy to VPN Only
-
Restrict Backend:
Edit
.envinappflowy_backend:APP_PORT=8080 APP_HOST=10.0.0.1
Restart backend:
sudo systemctl restart appflowy
-
Restrict Nginx:
Edit
/etc/nginx/sites-available/appflowy:server { listen 10.0.0.1:80; ... }Then reload:
sudo nginx -t && sudo systemctl reload nginx
-
-
Lock Down the Firewall
Block public access:
sudo ufw allow 51820/udp sudo ufw allow from 10.0.0.0/24 to any port 80 sudo ufw allow from 10.0.0.0/24 to any port 8080 sudo ufw deny 80/tcp sudo ufw deny 8080/tcp
Enable UFW:
sudo ufw enable
-
Validate
From your client (10.0.0.2):
curl http://10.0.0.1 # AppFlowy frontend curl http://10.0.0.1:8080 # AppFlowy backend
From public IP:
curl http://your-vps-ip # Should fail (timeout or connection refused)
Benefits of This Setup
- AppFlowy is only accessible via VPN (zero public exposure)
- Lightweight and fast via WireGuard
- Ideal for private/internal workflows or early-stage dev
Let’s now set up remote backups over a WireGuard VPN — allowing your AppFlowy VPS to securely send backup files to a remote backup server (or NAS) without ever exposing any services to the public internet.
-
Remote Backups over WireGuard VPN
We’ll configure:
- A backup server (WireGuard peer with SSH/SFTP)
- The VPS to connect to it via WireGuard
- An automated
rsync-based backup script - (Optional) cron job for scheduled backups
Assumptions
Role WireGuard IP Function AppFlowy VPS 10.0.0.1Source of backups Backup Server 10.0.0.2Remote storage via SSH/SFTP -
Set Up WireGuard on the Backup Server
sudo apt install wireguard -y
Generate keypair:
wg genkey | tee privatekey | wg pubkey > publickey
Configure
/etc/wireguard/wg0.conf:[Interface] Address = 10.0.0.2/24 ListenPort = 51820 PrivateKey = [Peer] PublicKey = AllowedIPs = 10.0.0.1/32
Start and enable WireGuard:
sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0
-
VPS WireGuard Config Update
On the VPS
/etc/wireguard/wg0.conf:[Peer] PublicKey = AllowedIPs = 10.0.0.2/32 Endpoint = :51820 PersistentKeepalive = 25
Restart interface:
sudo systemctl restart wg-quick@wg0
Ping the remote backup host:
ping 10.0.0.2
-
Create Backup User on Backup Server
On backup server:
sudo adduser appflowy_backup
Generate SSH key on VPS:
ssh-keygen -t ed25519 -f ~/.ssh/backup_vpn_key
Copy the public key to backup server:
ssh-copy-id -i ~/.ssh/backup_vpn_key.pub appflowy_backup@10.0.0.2
Test SSH login:
ssh -i ~/.ssh/backup_vpn_key appflowy_backup@10.0.0.2
-
Rsync-Based Backup Script
On the VPS, create:
nano ~/rsync_appflowy_backup.sh
Paste:
#!/bin/bash APPFLOWY_DIR="$HOME/AppFlowy" BACKUP_TARGET_DIR="/home/appflowy_backup/appflowy_data" REMOTE_USER="appflowy_backup" REMOTE_HOST="10.0.0.2" SSH_KEY="$HOME/.ssh/backup_vpn_key" # Generate backup archive TIMESTAMP=$(date +"%Y-%m-%d_%H-%M") ARCHIVE="/tmp/appflowy_backup_$TIMESTAMP.tar.gz" tar -czf "$ARCHIVE" \ "$APPFLOWY_DIR/appflowy_backend/appflowy.db" \ "$APPFLOWY_DIR/appflowy_backend/.env" \ "$APPFLOWY_DIR/frontend/build/web" # Transfer over VPN using rsync rsync -avz -e "ssh -i $SSH_KEY" "$ARCHIVE" "${REMOTE_USER}@${REMOTE_HOST}:${BACKUP_TARGET_DIR}/" # Cleanup local archive rm -f "$ARCHIVE" echo "Backup sent to VPN peer at $REMOTE_HOST"Make executable:
chmod +x ~/rsync_appflowy_backup.sh
-
Automate via
cronRun daily at 3:30 AM:
crontab -e
Add:
30 3 * * * /home/YOURUSER/rsync_appflowy_backup.sh >> /home/YOURUSER/backup_log.txt 2>&1
Final Security Notes
- SSH over WireGuard ensures private, encrypted file transfer
- Backup server can be completely firewalled from public
- Add fail2ban + key-only login for SSH hardening
-
Interactive AppFlowy Install Script
Here’s an interactive version of the AppFlowy install script. It prompts the user to input server-specific values like:
Hostname/domain nameAppFlowy install directoryBackend portUsername to run the service
This version improves user experience while keeping automation intact.
Script:
install_appflowy_interactive.sh#!/bin/bash set -e echo "AppFlowy VPS Installation Script (Interactive Mode)" echo "-------------------------------------------------------" # === INTERACTIVE INPUT === read -rp "Enter your domain name or server IP (e.g., example.com): " DOMAIN_OR_IP read -rp "Enter AppFlowy install directory (default: \$HOME/AppFlowy): " INSTALL_DIR INSTALL_DIR="${INSTALL_DIR:-$HOME/AppFlowy}" read -rp "Enter backend port (default: 8080): " BACKEND_PORT BACKEND_PORT="${BACKEND_PORT:-8080}" read -rp "Enter the Linux username to run AppFlowy (default: $(whoami)): " RUN_USER RUN_USER="${RUN_USER:-$(whoami)}" echo -e "\n Installing dependencies..." sudo apt update && sudo apt upgrade -y sudo apt install -y git curl unzip build-essential libssl-dev pkg-config libclang-dev cmake nginx echo "🔧 Installing Rust..." curl https://sh.rustup.rs -sSf | sh -s -- -y source "$HOME/.cargo/env" echo "Installing Flutter..." git clone https://github.com/flutter/flutter.git -b stable "$HOME/flutter" echo 'export PATH="$PATH:$HOME/flutter/bin"' >> ~/.bashrc source ~/.bashrc flutter config --enable-web flutter doctor || true echo "Cloning AppFlowy to $INSTALL_DIR..." git clone https://github.com/AppFlowy-IO/AppFlowy.git "$INSTALL_DIR" echo "Building backend..." cd "$INSTALL_DIR/appflowy_backend" cp .env.example .env sed -i "s|APP_PORT=.*|APP_PORT=$BACKEND_PORT|" .env cargo build --release echo "Building frontend..." cd "$INSTALL_DIR/frontend" flutter pub get flutter build web echo "Configuring Nginx for $DOMAIN_OR_IP..." NGINX_CONF="/etc/nginx/sites-available/appflowy" sudo tee "$NGINX_CONF" > /dev/null < /dev/null <How to Use
- Save the script:
nano install_appflowy_interactive.sh
- Paste the content above.
- Make it executable:
chmod +x install_appflowy_interactive.sh
- Run:
./install_appflowy_interactive.sh
-
Interactive
.envGenerator for AppFlowy BackendLet’s now build a version of the installer that interactively generates a complete
.envfile for the AppFlowy backend, including options for:- Database backend: SQLite or PostgreSQL
- Backend binding address and port
- CORS origins (if you’re reverse proxying or separating frontend/backend)
- Storage directory override
Script:
generate_env_interactive.sh#!/bin/bash echo "AppFlowy .env File Generator" echo "------------------------------" # === DATABASE CHOICE === read -rp "Use SQLite or PostgreSQL? (sqlite/postgres) [sqlite]: " DB_TYPE DB_TYPE="${DB_TYPE:-sqlite}" if [[ "$DB_TYPE" == "postgres" ]]; then read -rp "Enter PostgreSQL connection string (e.g., postgres://user:pass@localhost:5432/db): " DB_URL else DB_URL="appflowy.db" fi # === BINDING ADDRESS === read -rp "Backend bind address (e.g., 127.0.0.1 or 0.0.0.0 or VPN IP) [127.0.0.1]: " APP_HOST APP_HOST="${APP_HOST:-127.0.0.1}" # === PORT === read -rp "Backend port [8080]: " APP_PORT APP_PORT="${APP_PORT:-8080}" # === CORS CONFIG === read -rp "Allowed frontend origin for CORS (e.g., http://localhost:3000 or https://yourdomain.com) [*]: " CORS_ORIGIN CORS_ORIGIN="${CORS_ORIGIN:-*}" # === STORAGE DIR === read -rp "Custom data storage directory (leave blank for default): " STORAGE_DIR # === GENERATE FILE === ENV_FILE=".env" echo "Writing $ENV_FILE..." cat > "$ENV_FILE" <> "$ENV_FILE" fi echo ".env file generated successfully!" cat "$ENV_FILE"What This Covers
Variable Description DATABASE_TYPEsqliteorpostgresDATABASE_URLPath or connection string APP_HOSTBinding IP (0.0.0.0, 127.0.0.1, VPN IP) APP_PORTListening port CORS_ALLOW_ORIGINPermitted origins for frontend calls APP_STORAGE_DIROptional override for where to store app data How to Use
- Save the script:
nano generate_env_interactive.sh
- Paste the contents above.
- Make executable and run inside
appflowy_backend:chmod +x generate_env_interactive.sh cd ~/AppFlowy/appflowy_backend ./generate_env_interactive.sh
This will generate a
.envfile ready forcargo buildandsystemdexecution.Let’s now fully integrate the
.envgenerator into the main install script and add input validation for:- Hostnames and IPs
- Ports (must be 1–65535)
- PostgreSQL URI format
- Acceptable database types (
sqlite/postgres)
Unified + Validated AppFlowy Installer:
install_appflowy_full.sh#!/bin/bash set -euo pipefail echo "AppFlowy Interactive Installation Script (with .env and Validation)" echo "-----------------------------------------------------------------------" # === INPUT FUNCTIONS === function ask_domain_or_ip() { while true; do read -rp "Enter your domain name or server IP (e.g., example.com): " DOMAIN_OR_IP if [[ "$DOMAIN_OR_IP" =~ ^([a-zA-Z0-9.-]+|\b([0-9]{1,3}\.){3}[0-9]{1,3}\b)$ ]]; then break else echo "Invalid domain or IP address. Try again." fi done } function ask_backend_port() { while true; do read -rp "Enter backend port (default: 8080): " BACKEND_PORT BACKEND_PORT="${BACKEND_PORT:-8080}" if [[ "$BACKEND_PORT" =~ ^[0-9]+$ && "$BACKEND_PORT" -ge 1 && "$BACKEND_PORT" -le 65535 ]]; then break else echo "Invalid port number. Must be between 1–65535." fi done } function ask_db_type() { while true; do read -rp "Use SQLite or PostgreSQL? (sqlite/postgres) [sqlite]: " DB_TYPE DB_TYPE="${DB_TYPE:-sqlite}" if [[ "$DB_TYPE" == "sqlite" || "$DB_TYPE" == "postgres" ]]; then break else echo "Please enter either 'sqlite' or 'postgres'." fi done } function ask_postgres_url() { while true; do read -rp "Enter PostgreSQL connection string (e.g., postgres://user:pass@localhost:5432/db): " DB_URL if [[ "$DB_URL" =~ ^postgres:\/\/.+:.+@.+:[0-9]+\/.+$ ]]; then break else echo "Invalid PostgreSQL URL format." fi done } # === USER PROMPTS === ask_domain_or_ip read -rp "Enter AppFlowy install directory (default: \$HOME/AppFlowy): " INSTALL_DIR INSTALL_DIR="${INSTALL_DIR:-$HOME/AppFlowy}" ask_backend_port read -rp "Backend bind address (e.g., 127.0.0.1 or 0.0.0.0) [127.0.0.1]: " APP_HOST APP_HOST="${APP_HOST:-127.0.0.1}" read -rp "Allowed frontend origin for CORS (e.g., https://domain.com) [*]: " CORS_ORIGIN CORS_ORIGIN="${CORS_ORIGIN:-*}" read -rp "Optional: custom data storage dir (leave blank for default): " STORAGE_DIR read -rp "Enter Linux user to run AppFlowy (default: $(whoami)): " RUN_USER RUN_USER="${RUN_USER:-$(whoami)}" ask_db_type if [[ "$DB_TYPE" == "postgres" ]]; then ask_postgres_url else DB_URL="appflowy.db" fi # === INSTALLATION === echo "Installing dependencies..." sudo apt update && sudo apt install -y git curl unzip build-essential libssl-dev pkg-config libclang-dev cmake nginx echo "Installing Rust..." curl https://sh.rustup.rs -sSf | sh -s -- -y source "$HOME/.cargo/env" echo "Installing Flutter..." git clone https://github.com/flutter/flutter.git -b stable "$HOME/flutter" echo 'export PATH="$PATH:$HOME/flutter/bin"' >> ~/.bashrc source ~/.bashrc flutter config --enable-web flutter doctor || true echo "Cloning AppFlowy..." git clone https://github.com/AppFlowy-IO/AppFlowy.git "$INSTALL_DIR" # === .env GENERATION === cd "$INSTALL_DIR/appflowy_backend" echo "Generating .env file..." cat > .env <> .env echo ".env file created:" cat .env # === BUILD & DEPLOY === echo "Building backend..." cargo build --release echo "Building frontend..." cd "$INSTALL_DIR/frontend" flutter pub get flutter build web echo "Configuring Nginx for $DOMAIN_OR_IP..." NGINX_CONF="/etc/nginx/sites-available/appflowy" sudo tee "$NGINX_CONF" > /dev/null < /dev/null <Summary of Improvements
- ✅ Full
.envcreation with validation - ✅ Prompted selection between SQLite/PostgreSQL
- ✅ Regex checks for ports, hostnames, and DB URLs
- ✅ Works with Nginx binding to VPN/private interfaces
-
Finalized AppFlowy Install Script
With:
- ✅ Interactive config & validation
- ✅
.envfile generation - ✅ Certbot auto-SSL + HTTP→HTTPS redirect
- ✅ Packaged for
curl | bashexecution
Deployable Script: Hosted Install (e.g.,
install_appflowy.sh)Here’s the final version of the script you can host on your own GitHub Gist, S3, or server and invoke like:
curl -fsSL https://yourdomain.com/install_appflowy.sh | bash
#!/bin/bash set -euo pipefail echo "AppFlowy Secure Installer with Certbot (Full Setup)" echo "------------------------------------------------------" # === INPUT VALIDATION FUNCTIONS === function ask_domain() { while true; do read -rp "Domain name (must resolve to this VPS): " DOMAIN if [[ "$DOMAIN" =~ ^([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$ ]]; then break else echo "Invalid domain format."; fi done } function ask_port() { while true; do read -rp "Backend port [8080]: " PORT PORT="${PORT:-8080}" if [[ "$PORT" =~ ^[0-9]+$ && "$PORT" -ge 1 && "$PORT" -le 65535 ]]; then break else echo "Invalid port (1–65535)"; fi done } function ask_db_type() { while true; do read -rp "Database type (sqlite/postgres) [sqlite]: " DB DB="${DB:-sqlite}" [[ "$DB" == "sqlite" || "$DB" == "postgres" ]] && break || echo "❌ Choose sqlite or postgres" done } function ask_pg_url() { while true; do read -rp "PostgreSQL URL: " DB_URL [[ "$DB_URL" =~ ^postgres:\/\/.+:.+@.+:[0-9]+\/.+$ ]] && break || echo "❌ Invalid format" done } # === PROMPTS === ask_domain read -rp "Install dir [\$HOME/AppFlowy]: " INSTALL_DIR INSTALL_DIR="${INSTALL_DIR:-$HOME/AppFlowy}" ask_port read -rp "Bind address [127.0.0.1]: " HOST HOST="${HOST:-127.0.0.1}" read -rp "Allowed CORS origin [*]: " CORS CORS="${CORS:-*}" read -rp "Service user [$(whoami)]: " USER USER="${USER:-$(whoami)}" ask_db_type [[ "$DB" == "postgres" ]] && ask_pg_url || DB_URL="appflowy.db" read -rp "Optional: custom storage dir: " STORAGE_DIR # === INSTALL DEPENDENCIES === sudo apt update sudo apt install -y git curl unzip build-essential libssl-dev pkg-config libclang-dev cmake nginx certbot python3-certbot-nginx echo "Rust..." curl https://sh.rustup.rs -sSf | sh -s -- -y source "$HOME/.cargo/env" echo "Flutter..." git clone https://github.com/flutter/flutter.git -b stable "$HOME/flutter" echo 'export PATH="$PATH:$HOME/flutter/bin"' >> ~/.bashrc source ~/.bashrc flutter config --enable-web flutter doctor || true echo "AppFlowy repo..." git clone https://github.com/AppFlowy-IO/AppFlowy.git "$INSTALL_DIR" # === CREATE .env === cd "$INSTALL_DIR/appflowy_backend" cat > .env <> .env # === BUILD === cargo build --release cd "$INSTALL_DIR/frontend" flutter pub get flutter build web # === NGINX SETUP === NGINX_CONF="/etc/nginx/sites-available/appflowy" sudo tee "$NGINX_CONF" > /dev/null < /dev/null <Deployment Instructions
- Host it on your server or repo:
- Place the script at
https://yourdomain.com/install_appflowy.sh
- Place the script at
- Make it downloadable and executable:
curl -fsSL https://yourdomain.com/install_appflowy.sh | bash
Bonus: Harden for Production
- Use
ufw allow OpenSSH && ufw allow 'Nginx Full' && ufw enable - Set
fail2banfor SSH brute-force protection - Add
systemdwatchdogs (Restart=always) for resilience
-
GitHub-Ready Installer Package for AppFlowy
We’ll generate:
- ✅
install_appflowy.sh— full secure interactive installer - ✅
uninstall_appflowy.sh— clean uninstaller - ✅
README.md— deployment instructions - ✅ Optional
.gitignore, license metadata
Suggested Repo Structure
appflowy-installer/ ├── install_appflowy.sh ├── uninstall_appflowy.sh ├── README.md └── .gitignore
install_appflowy.sh✅ Already created in previous step.
Make sure it haschmod +xand starts with a shebang (#!/bin/bash).uninstall_appflowy.sh#!/bin/bash echo "This will stop and remove AppFlowy, configs, and Nginx entries" read -rp "Continue? (y/n): " confirm [[ "$confirm" != "y" ]] && exit 0 echo "Stopping services..." sudo systemctl stop appflowy sudo systemctl disable appflowy sudo rm -f /etc/systemd/system/appflowy.service echo "Removing Nginx config..." sudo rm -f /etc/nginx/sites-enabled/appflowy sudo rm -f /etc/nginx/sites-available/appflowy sudo systemctl reload nginx read -rp "Delete AppFlowy install directory? (default: $HOME/AppFlowy) [y/N]: " deldir if [[ "$deldir" == "y" ]]; then rm -rf "$HOME/AppFlowy" fi echo "Uninstall complete."
README.md# AppFlowy Self-Hosted Installer This script installs a secure, production-ready version of [AppFlowy](https://github.com/AppFlowy-IO/AppFlowy) with HTTPS, systemd, and Nginx support. ## Features - Interactive `.env` generation - SQLite or PostgreSQL support - Certbot TLS/SSL via Let's Encrypt - Private or VPN-based Nginx binding - Systemd service configuration - Optional uninstaller script ## Quick Start ```bash curl -fsSL https://yourdomain.com/install_appflowy.sh | bash
Requirements
- Ubuntu 20.04+ VPS with sudo access
- A registered domain pointing to your server
- Ports 80 and 443 open (UFW:
sudo ufw allow 'Nginx Full')
Uninstall
./uninstall_appflowy.sh
Security
The script supports:
- HTTPS via Certbot
- CORS config
- Non-root system user
- Private VPN binding (127.0.0.1 or 10.0.0.x)
License
MIT License — feel free to fork and adapt.
--- ## 📄 `.gitignore` (Optional) ```gitignore *.tar.gz *.db .env backup_*.sh *.log
Deployment
You can now host the repo at:
- GitHub/GitLab (with raw URL support)
- Your own HTTPS server
- Or make a GitHub Gist for single-script mode
- ✅
Conclusion
You now know how to deploy self-hosted AppFlowy on Ubuntu VPS.









