Swap memory

Hi. Is there a way to configure swap memory for a Fly app? Or is this something that should configured in the Docker image (I’m using alpine:3.13)? I just SSHed into my instance and it looks like there is no swap configured.

# free -m
              total        used        free      shared  buff/cache   available
Mem:            228          83          69          35          74         117
Swap:             0           0           0

Thanks :slight_smile:

3 Likes

Hi, I’ve just confirmed a deploy based on alpine using this as the final line in my Dockerfile:

CMD if [[ ! -z "$SWAP" ]]; then fallocate -l $(($(stat -f -c "(%a*%s/10)*7" .))) _swapfile && mkswap _swapfile && swapon _swapfile && ls -hla; fi; free -m; /app/run

You then need to set SWAP="true" in your fly.toml to enable the swap partition. That flag is needed to allow the container to run correctly under your local docker server.

10 Likes

I realise this is now an old thread, but recently I was struggling to get a Rails app to reliably run on Alpine in a standard 256Mb fly VM, and after following the advice given here to add swap, the app has been running perfectly.

Now that it’s working, I’d like to understand a bit more about the specific formula used here to calculate the size of the swapfile (just to satisfy my own curiosity).

Apologies if I get some of the Bash / POSIX terminology wrong here.

Breaking it down:

fallocate -l <length in bytes> _swapfile

  • fallocate allocates space on disk
  • -l <length> is the number of bytes to allocate
  • _swapfile is the file name to assign to the allocated space

The length argument comes from the result of an arithmetic expansion (double parentheses), i.e. $(( )).

The arithmetic expression comes from the output of another (non-arithmetic) expansion, $( ).

The command in that subshell is:

stat -f -c <format> .

  • stat displays file or filesystem status
  • -f indicates that we’re interested in the filesystem (not file) status
  • -c <format> specifies a format for the output
  • . is the current directory

Finally, the format itself is:

"(%a*%s/10)*7"

  • %a is the number of free blocks available to non-superuser
  • %s is the block size (e.g. 4096)
  • 10 is a constant
  • 7 is a constant

…which can be rewritten as:

( <free blocks> * <block size> / 10 ) * 7

<free blocks> * <block size> obviously computes the total amount of free space (in bytes) available.

Am I right in assuming that the subsequent division by 10 and multiplication by 7 is simply a way of saying:

please allocate 70% of all remaining available space for swap

And I’m guessing that reasons for allocating a percentage of remaining available space (as opposed to allocating a fixed value, such as fallocate -l 512MB) are:

  1. Removes the risk of not having enough space left to allocate (assuming that fallocate will fail if the specified length exceeds available free space)
  2. Unused disk space in containers is basically wasted, so might as well use most of it for swap? (especially for web servers/apps that don’t write much, if anything, to disk after booting)

I suppose the only downside is that, since this has to be one of the last lines in the Dockerfile (as it needs to calculate available space after all of the code & dependencies are copied, that layer can’t be cached?

4 Likes

FYI, if people run fly launch on a Rails app these days, they get the following:

In other words, no formula, just a fixed size. One that can be tailored.

2 Likes

I was trying with this one but get the following error
swapon: _swapfile: Operation not permitted

May 3 09:09:48  8c673ca3  vector  Preparing to run: `/bin/sh -c if [[ ! -z "$SWAP" ]]; then fallocate -l $(($(stat -f -c "(%a*%s/10)*7" .))) _swapfile && chmod 0600 _swapfile && mkswap _swapfile && swapon _swapfile && ls -hla; fi; free -m; /app/bin/my_app start` as nobody
May 3 09:09:48  8c673ca3  vector  2023/05/03 16:09:48 listening on [fdaa:0:5db0:a7b:13f:8c67:3ca3:2]:22 (DNS: [fdaa::3]:53)
May 3 09:09:48  8c673ca3  vector  Setting up swapspace version 1, size = 5479625108 bytes
May 3 09:09:48  8c673ca3  vector  UUID=5eee0e55-475f-4b1f-894d-f06ddb1a3453
May 3 09:09:48  8c673ca3  vector  swapon: _swapfile: Operation not permitted
              total        used        free      shared  buff/cache   available
May 3 09:09:48  8c673ca3  vector  Mem:            473          16         435           0          22         445
May 3 09:09:48  8c673ca3  vector  Swap:             0           0           0

Does it still work on your end @scottohara @OldhamMade ?

This script needs to be run as root. You will need to remove USER statements from your Dockerfile (or end with USER root), and modify your script to test for root, setup the swapfile, change to nobody, and re-run the script to complete the remainder of the setup.

This sounds complicated, but it is made much clearer with an example: rails swap. Change line 11 from rails to nobody. Replace lines 14 on with the remainder of your scirpt.

1 Like

It’s a while ago now, but I recall struggling with similar permission issues; and in my case I solved it a slightly different way (which works for me, but has some limitations that may not work for your use case).

In my Dockerfile, I have the fallocate, chmod and mkswap commands as one of the first image build steps, e.g.

FROM ruby:${RUBY_VERSION}-alpine

RUN \
	fallocate -l 512M /swapfile; \
	chmod 0600 /swapfile; \
	mkswap /swapfile;

Then outside of the Dockerfile, I have a deploy script that runs fly deploy followed by some fly ssh console commands to configure the swapfile on the running container, e.g.

# Deploy
fly deploy --app <our app> --build-arg <arg1> --build-arg <argN>

# Configure swap
fly ssh console --command 'sysctl --write vm.swappiness=10'

# Enable swap
fly ssh console --command 'swapon /swapfile'

I found that trying to set the “swappiness” level and run “swapon” as image build steps (i.e. using RUN) doesn’t work; and I didn’t want to change my image’s default CMD or ENTRYPOINT to run as root.

The limitation of this approach is that if you run more that one instance of the app (scale > 1); then you would need to enable swap on every instance; and if an instance is killed and respawns, the new instance won’t have swap enabled.

In my case, I’m only running at a scale of 1 and its a non-critical app, so I can live with these limitations.

I think @rubys suggestion is probably the correct way; but if I understand correctly it does require you to run your containers with root privileges, which we understand is not generally recommended if security is a concern.

While the script runs initially with root privileges, once swap is set up it switches users and your deployed app will be run under the unprivileged user.

1 Like

This worked for me, Thanks.
btw, I executed the fallocate, chmod and mkswap commands in the ssh console itself.
fly ssh console --command 'fallocate -l 512M /swapfile'
fly ssh console --command 'chmod 0600 /swapfile'
fly ssh console --command 'mkswap /swapfile'
fly ssh console --command 'sysctl -w vm.swappiness=10'
fly ssh console --command 'swapon /swapfile'

I was wondering whether swap space constitutes towards the persistent storage or not? maybe @rubys would know.

@rubys, Please consider adding a swap option to the official Fly’s Phoenix dockerfile. I couldn’t open an issue on github as you suggested here since its rules requrie to post requests only on the forum.

1 Like

Just realise there is a swap memory option in fly.tml which I used and check using free -m does setup swap correctly: Fly Launch configuration (fly.toml) · Fly Docs

3 Likes

Interesting, did I miss an announcement about it?

PS. I check flyctl sources on github, it seems this option has been added only 3 weeks ago

1 Like

Can anyone confirm if setting the below in fly.toml

swap_size_mb = 512

is the same as (as described in the docs)

fallocate -l 512M /swapfile
chmod 0600 /swapfile
mkswap /swapfile
echo 10 > /proc/sys/vm/swappiness
swapon /swapfile
echo 1 > /proc/sys/vm/overcommit_memory
1 Like

Doesn’t look like it echo 1 > /proc/sys/vm/overcommit_memory is done, but the rest are the same.

1 Like

Sentence doesn’t parse for me. Did you mean?