
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.
π§Ύ 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
-
π§ͺ Step 6: 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.









