Setting up a public FTP server on

Hi there,

I need to create a FTP server for our customers who don’t want to use our API (sigh).

I’ve tried to set up vsftpd on GCP but got lost in a GCP firewall/active mode mess. Seems like the big cloud providers aren’t interested in offering a full-managed solution for such an old piece of technology.

So, I want to have something as simple as possible: a FTP server in passive mode, secure if possible (either SFTP or FTPS, whichever is the simplest), and already configured with sane default so I can just add users.

Since I don’t need a full-fledged app, I was considering deploying through a simple Dockerfile.

Has anyone else done it before? Which Docker image should I choose?

Any advice appreciated.


If you haven’t already found one, I had a go at making an FTP server.

Take a look at GitHub - gregmsanderson/fly-ftp-server: A simple public FTP server for

The readme should explain how it works. The defaults should be sane :slight_smile:

For a quick job, SFTP/FTPS was beyond the scope. However that shouldn’t be too hard to add. For now it just uses plain FTP. Not ideal by any means. But it works: I tried it, uploaded a file, deleted it etc. Does the basics.


I can’t tell you how much you’ve helped me. This works exactly as intended, thank you so much!

1 Like

@greg just a question though: there seems to be two ways of creating users:

  1. through the USERS variable in
  2. through fly secrets set USERS="..."

Are they equivalent?

Yeah, that .sh script gets that referenced value of USERS from a variable. That is the secret value of USERS. I mean you can hard-code that value of USERS in there, if you want to. But generally it’s not a good idea to do that. As that would include a password, which you generally want to keep secret. Out of repos etc.

Since variables can be provided by Fly in multiple ways: the [env] block of the fly.toml, in the command line with -e, or via secrets. You’ll see the [env] was used here for non-secret things, like the port stuff. The USERS could be set in there too. It would work in exactly the same way. But again, that would exposes its contents to anyone who has access to your code. Using a secret keeps it, er, secret :slight_smile:

Ok, makes sense. So in, this line

if [ -z "$USERS" ]; then

is read only if no USERS env variable has been defined through fly secrets set, otherwise it’s not read at all.

Thanks for the explanation!

1 Like

Hi @greg

I tried to deploy the “fly-ftp-server” project you left in the previous post.
I am new to using both and Docker, but even so I have tried several ways to run it and I keep getting the same error. I show it, below:

I’ve repeated the execution steps explained in the “” file over and over again, but I still can’t deploy it.

Can you think of any way to fix it?
Am I missing some kind of software in docker or external software?

Thanks in advance.

Hi @pablosanchezl

Strange … I literally just deployed it, as I was documenting the ADDRESS option and swapping over IPv4/IPv6, as a result of @fdeage 's findings. So it does work :slight_smile:

The question is why it doesn’t for you. Hmm. Given it says file not found, it certainly suggests this is not happening for you:

Since if that file was being copied, well it wouldn’t say that. :slight_smile: The question is why not. Either the file is not on your system to copy, or it’s not being copied to the right place.

So … do you have that file locally, relative to your app folder? So you are running fly launch in the folder with a sub-folder called conf with that file in?

If so, it must be there locally. So not being copied in the Dockerfile. Hmm. I do notice the lack of a WORKDIR folder in the Dockerfile. However that doesn’t seem to matter (like I say, it works for me), plus I didn’t remove one that was there before. So that shouldn’t be needed. Hmm. Yet I wonder if it is in certain cases.

Somehow you need to get that startup .sh script to be where the code thinks it is. But not sure how beyond what I’m already doing, as that should work. As it does. But not for you! Weird.

Hi again @greg, I have continued testing throughout the past day and have not been able to solve the real problem. And I have come up with the following reasoning:

  1. The operating system may have an influence: I am now using Windows 10.
  2. I’m running the git project you made without significant changes (address and bug_level="debug", for my app). I go to the folder where I have the project and run all the commands: fly launch, fly volumes create, …, fly deploy, from the Powershell provided by Windows and the Docker Desktop program open.

working path

  1. Some version is different from the one you used for deployment.
  2. I have changed the ENTRYPOINT directive several times and I have observed that the file: /bin/ is found, but for some reason it does not execute it.
  3. It may be a problem with the deployment region, although I have changed it several times (London and Madrid).
  4. Problems with Docker or its configuration, but being inexperienced I wouldn’t know what to tell you.

Global error in Madrid (mad) deploy:

Global console error:

Now, I change ENTRYPOINT: ENTRYPOINT ["/sbin/tini", "--", "ls", "-l", "/bin/"]. Even though I have changed the directive the server is still not deployed.

And this new error occurs:

But the most surprising thing is when I change the ENTRYPOINT directive again, as follows: ENTRYPOINT ["/sbin/tini", "--", "/bin/sh", "/bin/"]. Now you will see something surprising.

It is not recognising shell script instructions!
Therefore, it can be said that this file is inside the system, but something is not working properly from my computer/region/ Right now, I don’t know what to do with this project.

:slight_smile: :upside_down_face:

As nothing I do is working and if after this answer you don’t know what is happening, I have thought to upload an express-node project, listening one port for https and backend requests, while another port is listening an ftp server made with node, with the installation package “ftp-srv”, which was my initial idea, but I didn’t know how to do it. I hope this experience will help me. :slight_smile:

Hi @pablosanchezl

Er …

I did have two thoughts:

The .dockerignore file in the repo. Have you changed that file for any reason? That needs to include the (two) files in conf. Mine does, but if you have changed that for any reason, the .sh file won’t be in your image despite being in your local folder.

… if it’s not that then …

You mention running Docker locally. I don’t. I’m on a Mac and it sounds like a jet engine with the fan. Never did sort that. So I just use Fly’s remote builder for my building. So that is certainly something that is different between our setups … you are running on Windows and building locally (since if Fly’s CLI spots you have docker running locally, it defaults to using it, as normally that would be what you want). But I’m on a Mac, building remotely. We can take the OS variable out here if you also build remotely. Since then we have the same region (in my case, lhr), and same builder. And hence will get the same file permissions :rocket: And if then it doesn’t work, well it truly is: :upside_down_face:

So either close Docker (so it’s not running before you run fly launch or fly deploy and so Fly will default to using a remote builder) or leave Docker running and use e.g fly launch --remote-only. One or the other.

Either way, a remote build will then happen. I think the Fly CLI output should include the remote builder’s name (random long thing) so it should be clear that is what’s happening.

That will avoid all the nodejs stuff too. Which (probably) would work. But so should this. :slight_smile:

There seems to be something wrong with my system to make it not work. I uninstalled Docker, removed and cloned the repository again, and started the steps again. And, this is what happened:

  1. Commands in order:
  • fly auth login

  • fly launch --remote-only

  • fly volumes create ftp_data --region lhr --size 1

  • fly secrets set USERS=“aaaa|aaaa|/data/aaaa”

  • fly deploy --remote-only or fly launch --remote-only

  1. Console logs:
  • Launch


  • Volumes

  • Secrets users

  • Deploy (1/3)

  • Deploy (2/3)

*Deploy (3/3)

The error is the same:

[FATAL tini (529)] exec /bin/ failed: No such file or directory
Main child exited normally with code: 127

So, there is nothing more to do, thank you very much for your help @greg. I will try to make the express-node + ftp-node project and upload it, but seeing what has happened I don’t know if it will work.


1 Like

@pablosanchezl Baffling. I just deployed it myself, again, and it worked, again. Using a remote builder, with lhr. All fine. I can see from your latest output that the scripts are being copied because the builder output shows the Dockerfile commands to do that running. And if they weren’t, the subsequent chmod commands would of course fail. But they don’t. The build works. So that part is fine. It’s just for some reason, running the server is not working for you :thinking:

My output (have made the volume and secret):

$ fly deploy
Update available 0.0.332 -> v0.0.333.
Run "fly version update" to upgrade.
==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-name-here ready
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.12 linux x86_64
Sending build context to Docker daemon  3.148kB
[+] Building 0.9s (12/12) FINISHED                                                                                                                                                                         
 => CACHED [internal] load remote build context                                                                                                                                                       0.0s
 => CACHED copy /context /                                                                                                                                                                            0.0s
 => [internal] load metadata for                                                                                                                                        0.8s
 => [pidproxy 1/2] FROM                                                                         0.0s
 => CACHED [pidproxy 2/2] RUN apk add alpine-sdk  && git clone  && cd pidproxy  && git checkout 193e5080e3e9b733a59e25d8f7ec84aee374b9bb  && sed -i 's/-mt  0.0s
 => CACHED [stage-1 2/7] COPY --from=pidproxy /usr/bin/pidproxy /usr/bin/pidproxy                                                                                                                     0.0s
 => CACHED [stage-1 3/7] RUN apk add vsftpd tini                                                                                                                                                      0.0s
 => CACHED [stage-1 4/7] COPY conf/ /bin/                                                                                                                               0.0s
 => CACHED [stage-1 5/7] COPY conf/vsftpd.conf /etc/vsftpd/vsftpd.conf                                                                                                                                0.0s
 => CACHED [stage-1 6/7] RUN chmod +x /bin/                                                                                                                                            0.0s
 => CACHED [stage-1 7/7] RUN chown root /etc/vsftpd/vsftpd.conf                                                                                                                                       0.0s
 => exporting to image                                                                                                                                                                                0.0s
 => => exporting layers                                                                                                                                                                               0.0s
 => => writing image sha256:hashhere                                                                                                          0.0s
 => => naming to                                                                                                                                0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository []
25de585ffd9c: Layer already exists 
589877c1a141: Layer already exists 
059ce14485bd: Layer already exists 
7ede06dc104b: Layer already exists 
8d282b519828: Layer already exists 
5f4d9bb710bd: Layer already exists 
4fc242d58285: Layer already exists 
deployment-12345: digest: sha256:hash size: 1778
--> Pushing image done
image size: 9.2 MB
==> Creating release
--> release v37 created

--> You can detach the terminal anytime without stopping the deployment
==> Monitoring deployment

 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v37 deployed successfully

So the healthcheck is passing, and hence the deploy completes.

If I run fly logs with an example user:

2022-06-10T14:10:47Z runner[13f1f68b] lhr [info]Starting instance
2022-06-10T14:10:49Z runner[13f1f68b] lhr [info]Configuring virtual machine
2022-06-10T14:10:49Z runner[13f1f68b] lhr [info]Pulling container image
2022-06-10T14:10:49Z runner[13f1f68b] lhr [info]Unpacking image
2022-06-10T14:10:49Z runner[13f1f68b] lhr [info]Preparing kernel init
2022-06-10T14:10:50Z runner[13f1f68b] lhr [info]Setting up volume 'ftp_data'
2022-06-10T14:10:50Z runner[13f1f68b] lhr [info]Opening encrypted volume
2022-06-10T14:10:50Z runner[13f1f68b] lhr [info]Configuring firecracker
2022-06-10T14:10:50Z runner[13f1f68b] lhr [info]Starting virtual machine
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Starting init (commit: e3eb6d2)...
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Mounting /dev/vdc at /data w/ uid: 0, gid: 0 and chmod 0755
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Preparing to run: `/sbin/tini -- /bin/` as root
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]2022/06/10 14:10:50 listening on [fdaa:0:abc:a7b:2809:1:1234:2]:22 (DNS: [fdaa::3]:1234)
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Set up user(s)
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Add user
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Changing password for example
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]New password:
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Retype password:
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]passwd: password for example changed by root
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Make their home folder
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Make sure they own that folder
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Created user(s)
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Start vsftpd
2022-06-10T14:10:50Z app[13f1f68b] lhr [info]Run pidproxy armed with the pid of vsftpd
2022-06-10T14:10:52Z app[13f1f68b] lhr [info]Fri Jun 10 14:10:52 2022 [pid 2] CONNECT: Client ""
2022-06-10T14:10:57Z app[13f1f68b] lhr [info]Fri Jun 10 14:10:57 2022 [pid 2] CONNECT: Client ""
2022-06-10T14:11:07Z app[13f1f68b] lhr [info]Fri Jun 10 14:11:07 2022 [pid 2] CONNECT: Client ""

… with those final three connections coming from Fly’s proxy as part of its healthcheck. So it can connect. For me anyway.

If you ever did want to come back to this, what I would do next is comment out the healthcheck in the fly.toml and so the deploy would (should) complete without that. And then fly ssh console to the vm to see what’s going on. Look at its processes. Look at the /data folder. See its innards. But you can’t spend infinite time on this so your nodejs may be done faster to get working than that.


It finally worked! :upside_down_face:

Searching in the community threads I saw that I might have problems with the file: ~/.fly/config.yml. I have reinstalled flyctl and downloaded the repository again. And in the end it was successfully deployed. What a silly problem. :man_facepalming:

Here is the post:

Thank you very much for all your answers @greg and your consideration. Now I just need to configure the server to be able to use SFTP/FTPS, I’ll see how I can do it. I’ll have to do some research.

Thanks again and have a nice day. :slight_smile:

P.D.:Finally, here is the proof that it works hahaha.

1 Like


Glad you got it sorted.

Well SFTP would be an entirely different approach. But FTPS should (in theory) work, with some changes. Either using Fly’s TLS handler or continue to use tcp-as-is and use your own certificate.