How to Deploy copyparty on a VPS
Updated Oct 2026
verified on Ubuntu 26.04 · Oct 2026Self-host copyparty on a small VPS — the single-file Python file server with uploads, WebDAV and a media-aware web UI, run in Docker Compose behind Caddy HTTPS with a restore-tested backup and a pinned-image upgrade path.
- A VPS with 512 MB+ RAM (copyparty's own floor is 256 MB; the extra room is for Docker and the reverse proxy)
- A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
- A domain or subdomain you can point at the server
What copyparty is
copyparty is a portable file server: you point it at a folder and it serves that folder over a web UI, with resumable uploads, deduplication, thumbnails, an audio player, a text and image viewer, and WebDAV, FTP and SFTP-style access for clients that prefer them. It is written in Python, MIT-licensed and rated 1 / 5 to deploy. If Nextcloud is more platform than you want and you just need "a place to put files that I and a few other people can reach from a browser", this is a strong fit.
This guide runs the official Docker image with Docker Compose, with Caddy in front for automatic HTTPS. Only one account exists, an administrator, and nothing is readable without logging in, which is the safe default for a server on the public internet. It picks up from "you have a server" and ends with the file server on HTTPS, a restore-tested backup and an upgrade routine. For help choosing a provider and tier, see Best VPS for self-hosting.
Server sizing
copyparty is one of the lightest apps in the catalog. On our test box the container idled at roughly 37 MB of RAM and the image plus a fresh share used about 135 MB of disk. Docker and Caddy add a little on top, so a 512 MB VPS is enough for personal use and 1 GB is comfortable.
What really sizes the server is the data, not the app. Disk is the number to plan: your share lives on the VPS, so pick a plan with the storage you need, or attach block storage later. Thumbnailing large photo or video libraries is the only thing that makes copyparty CPU- or RAM-hungry.
Prepare the server
Update the system and install the few tools this guide uses:
sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y curl openssl ufw dnsutils
If you're logged in as root, create a non-root sudo user and continue as that user:
adduser deploy
usermod -aG sudo deploy
Then open only SSH and the two ports the reverse proxy needs:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
The file server will be reachable only through the reverse proxy. That matters
on a Docker host: a published container port goes around ufw entirely, so the
Compose file below binds copyparty to 127.0.0.1, and Caddy becomes the only
public door.
Paid link — we earn a commission if you shop through it.
Install Docker
copyparty is installed with Docker Compose. If Docker isn't on the server yet, the convenience script installs Docker Engine and the Compose plugin (the Docker and Compose on Ubuntu guide covers the details):
command -v docker >/dev/null || curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
sudo docker compose version
The group change applies on your next login; until then the commands below use
sudo docker.
Install copyparty
Create the project directory and the folder to share, and generate the admin
password into a .env file that Compose reads automatically. The container
runs as user ID 1000, so that user must own the share. The [ -f .env ] guard
keeps the step safe to re-run, so a second pass never silently changes the
password under you. Set your hostname first (this guide uses files.example.com; replace it with yours):
mkdir -p ~/copyparty/share && cd ~/copyparty
sudo chown 1000:1000 share
DOMAIN=files.example.com
[ -f .env ] || printf 'CP_PASS=%s\nDOMAIN=%s\n' "$(openssl rand -hex 12)" "$DOMAIN" > .env
grep -c CP_PASS .env
Now the Compose file. It runs the official ac image (the variant with the
common optional features included) with the tag pinned, so an upgrade is a
deliberate edit and not a surprise. The two arguments after the image are the
whole configuration: -a defines the admin account, and -v /w::A,admin
shares the folder with that account alone, with full access. Caddy gets its
hostname from the same .env:
cd ~/copyparty
cat > docker-compose.yml <<'EOF'
services:
copyparty:
image: copyparty/ac:1.20.25
restart: unless-stopped
user: "1000:1000"
ports: ["127.0.0.1:3923:3923"]
volumes: ["./share:/w"]
command: ["-a", "admin:${CP_PASS}", "-v", "/w::A,admin"]
caddy:
image: caddy:2
restart: unless-stopped
ports: ["80:80", "443:443"]
environment:
DOMAIN: ${DOMAIN}
volumes: ["./Caddyfile:/etc/caddy/Caddyfile:ro", "caddy_data:/data"]
depends_on: [copyparty]
volumes:
caddy_data:
EOF
cat > Caddyfile <<'EOF'
{$DOMAIN} {
reverse_proxy copyparty:3923
}
EOF
Start it and wait for the web UI to answer:
cd ~/copyparty
sudo docker compose up -d
for i in $(seq 1 30); do
code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3923/)
[ "$code" != "000" ] && break
sleep 2
done
echo "copyparty answered HTTP $code"
sudo docker compose ps
An HTTP 200 here is the login page, not your files: the share itself is
refused to anyone who isn't logged in, as the upload test below shows. Show the
admin password once so you can log in:
grep CP_PASS ~/copyparty/.env
HTTPS + domain
Create an A record for your file server's hostname (say
files.example.com) pointing at the server's public IP, and confirm it
resolves before Caddy tries to get a certificate:
dig +short files.example.com
Caddy is already running from the previous step. It requests the certificate as soon as the name resolves, and renews it on its own (see Automatic HTTPS with Caddy for how that works). Check that HTTPS is up and the login still protects the share:
. ~/copyparty/.env
sleep 15
curl -s -o /dev/null -w 'HTTPS status %{http_code}, certificate check %{ssl_verify_result}\n' \
"https://$DOMAIN/"
A ssl_verify_result of 0 means the certificate verified. Open
https://files.example.com in a browser and confirm the padlock.
First login and hardening
Open the site and log in as admin with the password from .env. Then:
- Upload a file from the browser to confirm writes work, and check it lands
in
~/copyparty/shareon the server. - Keep the single-account setup until you need more. To add people, add
further
-a user:passwordarguments and extra-vlines to thecommandin the Compose file, each with only the permissions that person needs (rread,wupload,Aeverything). Edit, thensudo docker compose up -d. - Keep the share's permissions narrow. Never give a public, anonymous account write access on an internet-facing server: open uploads get abused within days.
- The
.envfile holds the password in plain text. Keep it readable only by you (chmod 600 ~/copyparty/.env).
You can also use the same credentials from a command line. This uploads and fetches a file through the HTTPS front door, then confirms that an anonymous download of the same file is refused:
cd ~/copyparty
. ./.env
echo "hello copyparty" > /tmp/hello.txt
curl -s -o /dev/null -w 'upload HTTP %{http_code}\n' -u "admin:$CP_PASS" \
-T /tmp/hello.txt "https://$DOMAIN/hello.txt"
curl -s -u "admin:$CP_PASS" "https://$DOMAIN/hello.txt"
curl -s -o /dev/null -w 'anonymous download HTTP %{http_code}\n' "https://$DOMAIN/hello.txt"
Backups
Everything copyparty serves is in the share folder, and its settings are in
the Compose file and .env. There is no database to dump: the file index is
rebuilt from the folder. Archive the data and the configuration. The configuration archive contains .env, and with it the admin password, so the command also restricts both archives to their owner:
cd ~/copyparty
sudo tar czf ~/copyparty-share-$(date +%F).tar.gz share
sudo tar czf ~/copyparty-config-$(date +%F).tar.gz docker-compose.yml Caddyfile .env
sudo chmod 600 ~/copyparty-*.tar.gz
ls -lh ~/copyparty-*.tar.gz
Copy both archives off the server, to object storage, another machine,
anywhere that survives this VPS dying, and run the pair from cron. For a large
share, an incremental tool such as rsync or restic is a better fit than
re-archiving everything nightly. To prove the archive restores, unpack it into
a scratch folder and compare:
mkdir -p /tmp/cp-restore && cd /tmp/cp-restore
sudo tar xzf ~/copyparty-share-$(date +%F).tar.gz
sudo diff -r /tmp/cp-restore/share ~/copyparty/share && echo "restore matches"
sudo rm -rf /tmp/cp-restore
On a new server, install Docker, unpack the config archive into
~/copyparty, unpack the share archive beside it, make sure user ID 1000 owns
share, and run sudo docker compose up -d.
Upgrades
Because the image tag is pinned, an upgrade is an edit. Check copyparty's
release notes, take a backup
(previous section), change the tag in docker-compose.yml, then pull and
recreate:
cd ~/copyparty
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 20 copyparty
copyparty is a fast-moving project with frequent releases. There is no database migration to fear, so rolling back is just putting the previous tag back. Skim the release notes for changed command-line options before you jump several versions, since your whole configuration is command-line arguments.
Troubleshooting
Downloads return 403. That is the point: the only share requires the
admin account. Log in at the web UI, or pass
-u admin:PASSWORD to curl. If the correct password is rejected, check that
.env still holds the one you were shown and run
sudo docker compose up -d again, since the container reads it at start.
Uploads fail with "permission denied" in the logs. The share folder must
be owned by user ID 1000, the user the container runs as:
sudo chown -R 1000:1000 ~/copyparty/share.
Caddy never gets a certificate. Confirm dig +short files.example.com
returns the server's IP and that your provider's network-level firewall, not
just ufw, allows inbound 80 and 443. A blocked port 80 silently breaks the
ACME challenge. Read Caddy's side with sudo docker compose logs caddy.
Large uploads stall or restart. copyparty uploads in resumable chunks, so an interrupted browser upload continues where it stopped. If it keeps failing through the proxy, test directly over an SSH tunnel to port 3923 to separate a proxy problem from a server problem.
Verification + next steps
Not exercised in our test run: the new-user step and the DNS check against your real domain (we ran on a throwaway box with a temporary hostname); a reboot of the server, so the restart-after-reboot check below is expected behaviour from restart: unless-stopped, not something we observed.
You're done when https://files.example.com loads with a valid certificate,
anonymous downloads are refused, you can log in as admin and upload and
download a file, docker compose ps shows both services up after a reboot
(restart: unless-stopped brings them back), and you have restored a backup
into a scratch folder at least once.
From there, add per-person accounts and shares, try the WebDAV endpoint from your file manager, and set up a scheduled off-server copy of the archives. For the ranked server picks and the app's catalog details, see Best VPS for self-hosting and the copyparty entry.