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
| Project | kyantech/palmr |
| Last release | v3.3.2-beta (Dec 10, 2025) |
| Recommended Plan | RamNode KVM Premium SSD 2 GB / 50–100 GB; 4 GB if expecting concurrent multi-GB transfers |
| OS | Ubuntu 24.04 LTS |
| Stack | Docker Compose + Caddy 2 + SQLite (filesystem or S3 storage) |
Initial Server Setup
apt update && apt upgrade -y
apt install -y ca-certificates curl gnupg ufw fail2ban
adduser palmradmin
usermod -aG sudo palmradminCopy your SSH key into the new account, then test login as palmradmin in a second terminal before disconnecting from root. Then harden SSH:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yessystemctl reload ssh
ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enableInstall Docker + Compose
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 palmradminLog out + back in. Verify with docker version and docker compose version.
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:
dig +short palmr.example.comLay Out the Deployment + Keys
mkdir -p ~/palmr/{data,caddy/data,caddy/config}
cd ~/palmropenssl rand -base64 32Decide upfront whether to enable filesystem encryption — switching modes after files exist makes existing files inaccessible.
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: bridgeSECURE_SITE=truetells 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.
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.
Launch
cd ~/palmr
docker compose pull
docker compose up -d
docker compose logs -fFirst 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:
docker compose logs caddy | grep -i "certificate obtained"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.
Backups with Restic
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#!/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 --prunesudo 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>&1Brief 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.
Updates
cd ~/palmr
docker compose pull
docker compose up -dGiven upstream is archived, you'll likely want to pin to the final release once stable:
image: kyantech/palmr:v3.3.2-betaCommon issues
- "Unknown error" on uploads: body size limit. Check Caddy's
request_body max_size. Nginx equivalent isclient_max_body_size 50G;. - "readonly database": UID/GID mismatch. Check ownership of
~/palmr/dataagainstPALMR_UID/PALMR_GID. - Auth loops / cookies not sticking:
SECURE_SITE=truerequired over HTTPS, and your proxy must forwardX-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 palmrfor actual errors beyond the "please be patient" message.
