...
Deploy self-hosted appflowy on ubuntu vps
Learn how to deploy self-hosted appflowy on ubuntu vps!

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 πŸ–₯️.

Table of Contents

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.
Launch 100% ssd ubuntu vps from $3. 19/mo!

🧾 Deploy Self-Hosted AppFlowy on Ubuntu VPS

To deploy self-hosted AppFlowy on Ubuntu VPS, follow the steps below:

  1. πŸ“¦ 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.

  2. 🐍 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
    
  3. πŸ¦‹ 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.

  4. πŸ—‚οΈ Clone AppFlowy Repository

    git clone https://github.com/AppFlowy-IO/AppFlowy.git
    cd AppFlowy
    
  5. πŸ”§ Build AppFlowy Backend (Rust)

    cd appflowy_backend
    cargo build --release
    

    This builds the core backend binary you’ll be using.

  6. 🧱 Setup Backend Configuration

    Create a .env file in the appflowy_backend directory:

    cp .env.example .env
    nano .env
    

    Configure database type (default is sqlite). You can change DATABASE_URL if you want to use PostgreSQL.

    Example:

    DATABASE_TYPE=sqlite
    DATABASE_URL=appflowy.db
    APP_PORT=8080
    
  7. ▢️ Run the Backend Server

    ./target/release/appflowy_backend
    

    This will start the backend API on port 8080. You can daemonize this using systemd or tmux later.

  8. 🎨 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/
    
  9. 🌐 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
    
  10. πŸ” 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
    
  11. πŸ” 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 +short
    SSL cert fails Ensure no typos in domain and that Nginx is running properly
    Backend 502 errors Check systemctl status appflowy and journalctl -xe logs
  12. πŸ’Ύ 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:

    1. AppFlowy data (appflowy.db or PostgreSQL dump)
    2. Configuration files
    3. 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

    1. Save:
      nano backup_appflowy.sh
      

      Paste the code above.

    2. Make it executable:
      chmod +x backup_appflowy.sh
      
    3. 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 awscli with credentials beforehand.

  13. πŸ” Restore Script for AppFlowy: restore_appflowy.sh

    Let’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.gz file created by backup_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

    1. Save:
         nano restore_appflowy.sh
      
    2. Paste the code above and make it executable:
         chmod +x restore_appflowy.sh
      
    3. 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, or rsync
    • πŸ“ Use LVM or ZFS snapshots for full-disk recovery (advanced)
  14. πŸ“ˆ 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:

    🧠 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/data
    

    Start 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 systemd Services (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:19999
    Uptime Kuma Uptime checks & alerts http://your-ip:3001
    systemd Automatic restarts on crash systemctl status appflowy
  15. ⚑ Performance Tuning AppFlowy on Ubuntu VPS

    We’ll focus on optimizing:

    1. Rust backend performance
    2. Flutter web serving via Nginx
    3. System-level tuning (RAM, swap, ulimits)
    4. Database tuning (if using PostgreSQL)

    Performance Tuning AppFlowy on Ubuntu VPS steps:

    1. 🧠 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
      
    2. 🌐 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
      
    3. πŸ’» 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
      
    4. πŸ—ƒοΈ Database Tuning (if using PostgreSQL)

      If you switched from SQLite to PostgreSQL, optimize with:

      πŸ”§ Use pgtune presets:

      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, and journalctl for 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
      
  16. πŸ›‘οΈ 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:

    1. WireGuard server on the VPS
    2. A peer (your client device)
    3. AppFlowy + Nginx to bind only to VPN interface
    4. Security validation and firewall isolation

    To Run AppFlowy behind a WireGuard VPN, follow the steps below:

    1. 🧱 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
      
    2. 🧾 Configure WireGuard Server (/etc/wireguard/wg0.conf)

      sudo nano /etc/wireguard/wg0.conf
      

      Paste (replace YOUR_SERVER_PUBLIC_IP and 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
      
    3. πŸ’» 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
      
    4. 🧠 Lock Down AppFlowy to VPN Only

      • βœ… Restrict Backend:

        Edit .env in appflowy_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
        
    5. πŸ”₯ 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
      
    6. πŸ§ͺ 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.

  17. 🧳 Remote Backups over WireGuard VPN

    We’ll configure:

    1. A backup server (WireGuard peer with SSH/SFTP)
    2. The VPS to connect to it via WireGuard
    3. An automated rsync-based backup script
    4. (Optional) cron job for scheduled backups

    πŸ“Œ Assumptions

    Role WireGuard IP Function
    AppFlowy VPS 10.0.0.1 Source of backups
    Backup Server 10.0.0.2 Remote storage via SSH/SFTP
    1. 🧱 Set Up WireGuard on the Backup Server

      Install WireGuard:

      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
      
    2. 🧠 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
      
    3. πŸ’Ύ 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
      
    4. πŸ” 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
      
    5. ⏱️ Automate via cron

      Run 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
  18. 🧾 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 name
    • AppFlowy install directory
    • Backend port
    • Username 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

    1. Save the script:
      nano install_appflowy_interactive.sh
      
    2. Paste the content above.
    3. Make it executable:
      chmod +x install_appflowy_interactive.sh
      
    4. Run:
      ./install_appflowy_interactive.sh
      
  19. πŸ“„ Interactive .env Generator for AppFlowy Backend

    Let’s now build a version of the installer that interactively generates a complete .env file 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_TYPE sqlite or postgres
    DATABASE_URL Path or connection string
    APP_HOST Binding IP (0.0.0.0, 127.0.0.1, VPN IP)
    APP_PORT Listening port
    CORS_ALLOW_ORIGIN Permitted origins for frontend calls
    APP_STORAGE_DIR Optional override for where to store app data

    πŸ§ͺ How to Use

    1. Save the script:
      nano generate_env_interactive.sh
      
    2. Paste the contents above.
    3. 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 .env file ready for cargo build and systemd execution.

    Let’s now fully integrate the .env generator 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 .env creation with validation
    • βœ… Prompted selection between SQLite/PostgreSQL
    • βœ… Regex checks for ports, hostnames, and DB URLs
    • βœ… Works with Nginx binding to VPN/private interfaces
  20. βœ… Finalized AppFlowy Install Script

    With:

    • βœ… Interactive config & validation
    • βœ… .env file generation
    • βœ… Certbot auto-SSL + HTTPβ†’HTTPS redirect
    • βœ… Packaged for curl | bash execution

    πŸ“¦ 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

    1. Host it on your server or repo:
      • Place the script at https://yourdomain.com/install_appflowy.sh
    2. 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 fail2ban for SSH brute-force protection
    • Add systemd watchdogs (Restart=always) for resilience
  21. πŸ“˜ GitHub-Ready Installer Package for AppFlowy

    We’ll generate:

    1. βœ… install_appflowy.sh β€” full secure interactive installer
    2. βœ… uninstall_appflowy.sh β€” clean uninstaller
    3. βœ… README.md β€” deployment instructions
    4. βœ… 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 has chmod +x and 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

    πŸ” 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

Launch 100% ssd ubuntu vps from $3. 19/mo!

Conclusion

You now know how to deploy self-hosted AppFlowy on Ubuntu VPS.

Avatar of editorial staff

Editorial Staff

Rad Web Hosting is a leading provider of web hosting, Cloud VPS, and Dedicated Servers in Dallas, TX.
lg