# copyparty stack -- caddy (TLS) + copyparty (portable file server). # # Topology: # Browser/WebDAV --> caddy:443 --> copyparty:3923 (HTTP, internal) # SFTP / FTPS --> copyparty:{3922,3990,passive} (direct, NOT via caddy) # # Caddy terminates TLS and reverse-proxies the web/WebDAV UI. copyparty's SFTP # and FTPS listeners are their own TCP services, so they are published straight # from the copyparty container -- Caddy is HTTP-only and not in that path. # # The image's baked-in config does `chdir /w` + `% /cfg` (include every *.conf in # /cfg), so our ./cfg/copyparty.conf is picked up automatically -- no `command:` # override needed. XDG_CONFIG_HOME=/cfg in the image, so generated SSH host keys # live under /cfg and persist across restarts (the bind-mount is read-write). name: copyparty volumes: caddy-data: caddy-config: services: # --------------------------------------------------------------------------- # Caddy -- TLS termination + reverse proxy for the web/WebDAV UI. The only # service on 80/443. Auto-issues a Let's Encrypt cert for ${COPYPARTY_DOMAIN}. # --------------------------------------------------------------------------- caddy: image: caddy:${CADDY_TAG:-2-alpine} container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" - "443:443/udp" # HTTP/3 volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config environment: COPYPARTY_DOMAIN: "${COPYPARTY_DOMAIN}" ACME_EMAIL: "${ACME_EMAIL}" depends_on: - copyparty healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"] interval: 30s timeout: 5s retries: 3 start_period: 10s # --------------------------------------------------------------------------- # copyparty -- the file server. HTTP 3923 stays internal (Caddy fronts it); # only the direct SFTP/FTPS listeners are published. Runs as PUID:PGID so # files on the host bind-mount have sane ownership. # # A published Docker port BYPASSES the host INPUT firewall, so set BIND_ADDR # to pin these listeners to a trusted interface if the box is multi-homed. # --------------------------------------------------------------------------- copyparty: image: ${COPYPARTY_IMAGE:-copyparty/ac}:${COPYPARTY_TAG:-latest} container_name: copyparty restart: unless-stopped user: "${PUID:-1000}:${PGID:-1000}" ports: - "${BIND_ADDR:-0.0.0.0}:${SFTP_PORT:-3922}:3922" # SFTP - "${BIND_ADDR:-0.0.0.0}:${FTPS_PORT:-3990}:3990" # FTPS (explicit TLS) - "${BIND_ADDR:-0.0.0.0}:${FTP_PASV_RANGE:-12000-12099}:${FTP_PASV_RANGE:-12000-12099}" # FTPS passive volumes: - ${DATA_DIR:-/srv/copyparty/data}:/w - ./cfg:/cfg healthcheck: # copyparty's own python -- no extra deps in the image. A successful TCP # connect to the HTTP listener means the server is up. test: ["CMD", "python3", "-c", "import socket; socket.create_connection(('127.0.0.1',3923),3).close()"] interval: 30s timeout: 5s retries: 3 start_period: 20s