Sign inSign up

bfren/ftps

By bfren

โ€ขUpdated 2 days ago

Simple FTP image based on Alpine Linux, with passive FTP over TLS support.

Image
Networking
Developer tools
7

100K+

bfren/ftps repository overview

โ FTPS

GitHub release (latest by date) Docker Pulls Docker Image Size GitHub Workflow Status

Docker Hubโ  - Quay.ioโ  - bfren ecosystemโ 

โ Introduction

An FTP server that only accepts encrypted connections (FTPS - FTP over TLS), for devices and apps that can't use SFTP. It runs vsftpdโ  with one user, kept inside /files, and creates a self-signed certificate if you don't provide one.

It is built on Alpine + S6โ , with vsftpd from Alpine's packages.

โ Quickstart

services:
  ftps:
    image: quay.io/bfren/ftps:5.2
    restart: unless-stopped
    ports:
      - "0.0.0.0:21:21"
      - "0.0.0.0:18700-18710:18700-18710"
    environment:
      - BF_FTPS_EXTERNAL_URI=ftp.example.com
      - BF_FTPS_VSFTPD_USER=fred
      - BF_FTPS_VSFTPD_PASS=${FTPS_PASS}
    volumes:
      - ./v/files:/files
      - ./v/ssl:/ssl

Run docker compose up -d, then connect with an FTP client, e.g. in FileZilla or WinSCP:

SettingValue
ProtocolFTP (not SFTP - that is a different protocol)
EncryptionExplicit FTP over TLS (not plain or implicit)
Hostftp.example.com - the address in BF_FTPS_EXTERNAL_*
Port21
User nameBF_FTPS_VSFTPD_USER
PasswordBF_FTPS_VSFTPD_PASS

Accept the certificate when the client asks - it is self-signed unless you provide your own.

โ Features

  • ๐Ÿ” Encryption required for logins and file transfers
  • ๐Ÿ“ One user, kept inside /files
  • ๐Ÿ“œ Self-signed certificate created on first start, or use your own
  • ๐ŸŒ Passive mode with a fixed port range, for firewalls and NAT
  • ๐Ÿฉบ Health check that logs in and uploads a file
  • ๐Ÿงช Built-in tests

โ Tags

Images are published to Docker Hub (bfren/ftps) and Quay.io (quay.io/bfren/ftps) for linux/amd64, linux/arm/v7 and linux/arm64, with vsftpd 3.0.5 on Alpine 3.24.

TagExampleUse when
latestYou always want the newest image
MAJOR5You want updates, but not breaking changes
MINOR5.2You want fixes only - recommended
VERSION5.2.0You want to pin this image exactly
vsftpdX, vsftpdX.Yvsftpd3.0You want a vsftpd version
vsftpdX.Y.Zvsftpd3.0.5You want an exact vsftpd release
vsftpdX.Y.Z-VERSIONvsftpd3.0.5-5.2.0You want to pin both

โ Configuration

โ Ports
PortPurpose
21FTP control connection - encrypted with explicit TLS
18700-18710Passive data connections (change with BF_FTPS_VSFTPD_*_PORT)

Publish the whole passive range, with the same numbers on the host and in the container.

โ Volumes
VolumePurpose
/filesThe user's files - owned by the user, who can't see anything outside it.
/sslvsftpd.pem - the certificate and key (created if it doesn't exist).
โ Environment Variables
VariableValuesDescriptionDefault
BF_FTPS_VSFTPD_USERstringRequired. The user name to log in with.
BF_FTPS_VSFTPD_PASSstringRequired. The user's password.
BF_FTPS_EXTERNAL_URIhost nameThe address clients use - told to clients for passive connections. Takes priority over BF_FTPS_EXTERNAL_IP.
BF_FTPS_EXTERNAL_IPIP addressThe address clients use, if you don't have a host name. One of the two is required.
BF_FTPS_VSFTPD_UIDnumberThe user's UID - set it to the owner of /files on the host.1000
BF_FTPS_VSFTPD_MIN_PORTportFirst port for passive data connections.18700
BF_FTPS_VSFTPD_MAX_PORTportLast port for passive data connections.18710
BF_FTPS_VSFTPD_WELCOMEtextMessage shown when users log in.Welcome to the FTPS server.
BF_FTPS_VSFTPD_ENABLE_DEBUG_LOG0, 1Log every FTP command and transfer to docker logs.0
BF_FTPS_VSFTPD_ENABLE_PASV_PROMISCUOUS0, 1Turn off vsftpd's check that data connections come from the same address as the login. Only for secure tunnels - leave it off.0
BF_FTPS_SSL_BITSnumberKey size for the self-signed certificate.4096
BF_FTPS_LFTP_VERIFY_CERTno, yesWhether the health check verifies the certificate - only yes with a trusted certificate.no

See Alpine + S6โ  for BF_DEBUG, BF_TZ and the other base image variables.

โ How It Works

On every start:

  1. The user, password and external address are checked - the container stops if any are missing.
  2. The user is created (if it doesn't exist yet) with /files as its home, and given /files.
  3. If /ssl/vsftpd.pem doesn't exist, a self-signed certificate valid for ten years is created there.
  4. vsftpd.conf is generated, and vsftpd runs under S6 once /ssl/vsftpd.pem has contents (see below). The container stops if it crashes.

Logins and file transfers must be encrypted, the user is kept inside /files (chroot), and anonymous logins are refused. Uploaded files are readable only by the user (umask 077).

In passive mode the client opens a second connection for each transfer, to a port in the passive range, at the address the server gives it - BF_FTPS_EXTERNAL_URI (looked up when vsftpd starts) or BF_FTPS_EXTERNAL_IP. That address must reach the server from the client's side.

The health check (every 30 seconds) logs in as the user with lftp, uploads a small file and deletes it.

โ Using your own certificate

Put a PEM file containing both the certificate and its private key at /ssl/vsftpd.pem, then restart. Set BF_FTPS_LFTP_VERIFY_CERT=yes if the certificate is trusted for 127.0.0.1.

To use a certificate from bfren/caddy-proxyโ , mount its PEM file for your host name:

    volumes:
      - ./proxy/ssl/certs/ftp.example.com/ftp.example.com.pem:/ssl/vsftpd.pem:ro

caddy-proxy creates an empty file for each host name until the certificate is obtained. While /ssl/vsftpd.pem is empty, vsftpd waits, checking every 30 seconds, and starts once it has contents - no restart is needed. If Docker has created a directory instead (because the file didn't exist), the log says so: remove the directory, then restart caddy-proxy (to create the file) and this container.

โ Testing

./test.nu   # builds the image and runs /test
./run.sh    # builds the image and opens a shell, with the ports published on 127.0.0.1

/test runs this image's tests alongside the base images' tests: the environment checks, creating the certificate, the generated vsftpd.conf and lftp.conf, and the installed vsftpd version.

โ Troubleshooting

โ The container stops straight after starting

Check docker logs <container> - BF_FTPS_VSFTPD_USER, BF_FTPS_VSFTPD_PASS, and BF_FTPS_EXTERNAL_URI or BF_FTPS_EXTERNAL_IP must all be set.

โ Logging in works, but listing files times out

The passive connection can't get through. Check the whole port range is published and allowed by your firewall, and that BF_FTPS_EXTERNAL_* is the address clients use to reach the server.

โ I changed the password but the old one still works

The user is only created once, so changes are ignored until the container is recreated (docker compose up -d --force-recreate).

โ The client says the certificate isn't trusted

It is self-signed. Accept it, or use your own certificateโ .

โ FAQs

โ Can I have more than one user?

No - one user per container. Run another container (with different ports) for another user.

โ What's the difference between FTPS and SFTP?

FTPS is FTP with TLS encryption. SFTP is a different protocol, part of SSH. This image only supports FTPS.

โ Licence

Copyright (c) 2021-2026 bfrenโ  (unless otherwise stated)

Tag summary

Content type

Image

Digest

sha256:550c50d42โ€ฆ

Size

30.6 MB

Last updated

2 days ago

docker pull bfren/ftps