File Sharing
    Open Source

    Deploy Palmr on a VPS

    A self-hosted WeTransfer alternative — password-protected shares, expiring links, OIDC, and a clean reverse-share workflow. Docker Compose + Caddy, ready in under an hour.

    Heads up: upstream is archived

    The Palmr GitHub repository was archived on Feb 27, 2026, with v3.3.2-beta as the final release. Docker images still pull and run cleanly and the software is functional, but no further upstream patches are expected. Watch for community forks before standing up a long-lived production deployment. The existing release is fine for personal or internal-team use.

    At a Glance

    Projectkyantech/palmr
    Last releasev3.3.2-beta (Dec 10, 2025)
    Recommended PlanRamNode KVM Premium SSD 2 GB / 50–100 GB; 4 GB if expecting concurrent multi-GB transfers
    OSUbuntu 24.04 LTS
    StackDocker Compose + Caddy 2 + SQLite (filesystem or S3 storage)
    1

    Initial Server Setup

    Update + create non-root admin
    apt update && apt upgrade -y
    apt install -y ca-certificates curl gnupg ufw fail2ban
    adduser palmradmin
    usermod -aG sudo palmradmin

    Copy your SSH key into the new account, then test login as palmradmin in a second terminal before disconnecting from root. Then harden SSH:

    /etc/ssh/sshd_config
    PermitRootLogin no
    PasswordAuthentication no
    PubkeyAuthentication yes
    Reload + firewall
    systemctl reload ssh
    
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow OpenSSH
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw enable
    2

    Install Docker + Compose

    Docker's official APT repo
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc
    
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    
    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    sudo usermod -aG docker palmradmin

    Log out + back in. Verify with docker version and docker compose version.

    3

    Configure DNS

    Add an A record for palmr.example.com pointing at your VPS IPv4 (and AAAA for IPv6 if you want it). Verify before continuing:

    DNS check
    dig +short palmr.example.com
    4

    Lay Out the Deployment + Keys

    Directory structure
    mkdir -p ~/palmr/{data,caddy/data,caddy/config}
    cd ~/palmr
    Generate encryption key (set even if disabled)
    openssl rand -base64 32

    Decide upfront whether to enable filesystem encryption — switching modes after files exist makes existing files inaccessible.

    5

    docker-compose.yaml

    ~/palmr/docker-compose.yaml
    services:
      palmr:
        image: kyantech/palmr:latest
        container_name: palmr
        restart: unless-stopped
        environment:
          - PALMR_UID=1000
          - PALMR_GID=1000
          - SECURE_SITE=true
          - DISABLE_FILESYSTEM_ENCRYPTION=true
          - ENCRYPTION_KEY=replace-with-output-of-openssl-rand
          - DEFAULT_LANGUAGE=en-US
          - DOWNLOAD_MAX_CONCURRENT=5
          - DOWNLOAD_MEMORY_THRESHOLD_MB=2048
        volumes:
          - ./data:/app/server/prisma
          - ./data/uploads:/app/server/uploads
        networks:
          - palmr_net
        expose:
          - "5487"
          - "3333"
    
      caddy:
        image: caddy:2
        container_name: palmr-caddy
        restart: unless-stopped
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile:ro
          - ./caddy/data:/data
          - ./caddy/config:/config
        networks:
          - palmr_net
        depends_on:
          - palmr
    
    networks:
      palmr_net:
        driver: bridge
    • SECURE_SITE=true tells Palmr it's behind HTTPS — required for secure session cookies.
    • UID/GID 1000 matches the typical first non-root user. Verify with id palmradmin. Mismatched UIDs are the #1 cause of "readonly database" SQLite errors.
    • The Palmr container exposes nothing to the host — all traffic flows through Caddy on the internal Docker network.
    6

    Caddyfile

    ~/palmr/Caddyfile
    palmr.example.com {
        encode zstd gzip
    
        reverse_proxy palmr:5487 {
            header_up X-Forwarded-Proto https
            header_up X-Forwarded-Host {host}
        }
    
        # Critical — Caddy defaults to a 10 MB body limit, which silently
        # rejects anything larger and produces baffling client-side errors.
        request_body {
            max_size 50GB
        }
    
        header {
            Strict-Transport-Security "max-age=31536000; includeSubDomains"
            X-Content-Type-Options "nosniff"
            X-Frame-Options "SAMEORIGIN"
            Referrer-Policy "strict-origin-when-cross-origin"
            -Server
        }
    
        tls you@example.com
    }

    Set request_body max_size to match the largest file you'll ever share. There is no penalty for being generous.

    7

    Launch

    Pull + start
    cd ~/palmr
    docker compose pull
    docker compose up -d
    docker compose logs -f

    First start runs DB migrations and takes ~5–6 minutes on a small VPS — the "Please be patient, Palmr is starting" message is normal. Verify Caddy got a cert:

    Cert check
    docker compose logs caddy | grep -i "certificate obtained"
    8

    First-Run Configuration

    Open https://palmr.example.com — the first user you register is auto-promoted to admin. Register your account first, then share the URL.

    • General: instance name, support email, branding.
    • SMTP: required for password resets + share notifications. Postmark, SendGrid, SES, Mailgun all work.
    • Auth providers: OIDC SSO via Google, Discord, GitHub, or any generic OIDC IdP including Authentik.
    • Storage: default is filesystem. Switch to S3-compatible before any uploads if you plan to grow beyond local disk — switching modes does not migrate existing files.
    • Share defaults: conservative expiration windows + password-by-default limits blast radius if a link leaks.
    9

    Backups with Restic

    Initialize repo
    sudo apt install -y restic
    
    export RESTIC_REPOSITORY="sftp:backup@backup.example.com:/backups/palmr"
    export RESTIC_PASSWORD="generate-a-strong-passphrase-and-store-it-safely"
    restic init
    /usr/local/bin/palmr-backup.sh
    #!/bin/bash
    # /usr/local/bin/palmr-backup.sh
    
    set -e
    export RESTIC_REPOSITORY="sftp:backup@backup.example.com:/backups/palmr"
    export RESTIC_PASSWORD_FILE="/root/.palmr-restic-pw"
    
    cd /home/palmradmin/palmr
    docker compose stop palmr
    restic backup ./data --tag palmr-daily
    docker compose start palmr
    restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
    Schedule
    sudo chmod +x /usr/local/bin/palmr-backup.sh
    sudo crontab -e
    # 30 3 * * * /usr/local/bin/palmr-backup.sh >> /var/log/palmr-backup.log 2>&1

    Brief downtime during the SQLite snapshot is preferable to corrupting an in-flight write. For a hot-backup option, run a .backup against the SQLite file before Restic'ing the uploads dir.

    10

    Updates

    Pull + recreate
    cd ~/palmr
    docker compose pull
    docker compose up -d

    Given upstream is archived, you'll likely want to pin to the final release once stable:

    Pin in compose
    image: kyantech/palmr:v3.3.2-beta

    Common issues

    • "Unknown error" on uploads: body size limit. Check Caddy's request_body max_size. Nginx equivalent is client_max_body_size 50G;.
    • "readonly database": UID/GID mismatch. Check ownership of ~/palmr/data against PALMR_UID / PALMR_GID.
    • Auth loops / cookies not sticking: SECURE_SITE=true required over HTTPS, and your proxy must forward X-Forwarded-Proto: https.
    • Slow uploads: if Cloudflare is in front, the orange-cloud proxy caps uploads at 100 MB (free) / 500 MB (Pro). Switch to grey-cloud DNS-only.
    • First start hangs >15 min: docker compose logs palmr for actual errors beyond the "please be patient" message.