I was having cost issues with the Render server, and I can't reduce the server for small machines. Most of my users are from Brazil, so I moved my Strapi to Fly.io, which enables reduced costs.
Fly.io better manages your VM using Firecracker, and I can choose servers located in Brazil (Guarulhos—Sao Paulo). It will provide a better experience for my users and better maintainability.
In this article, you will see all my steps to accomplish this migration.
My previous infrastructure was:
Render (Cost US$60.25/month)
Service | Memory | Disk | CPU |
---|---|---|---|
PostgreSQL | 1 GB RAM | 16 GB (using only 96 MB) | 1 CPU |
Strapi (Node) | 2 GB RAM (using only 600MB) | 1 GB (using only 0.28GB) | 1 CPU |
Meilisearch | 1 GB RAM (using only 60MB) | 1 CPU |
Now ...
Fly.io (Cost US$9.16/month)
Service | Memory | Disk | CPU |
---|---|---|---|
PostgreSQL | 256 MB RAM | 1 GB | 1 (shared) |
Strapi (Node) | 1 GB RAM | 1 GB | 1 (shared) |
Meilisearch | 1 GB RAM | 1 (shared) |
Deploy Strapi in Fly.io
Install Fly CLI
brew install flyctl
Login to your Fly account.
Follow the instructions and return to the command line.
fly auth login
Create the fly.toml
, dockerfile
and .dockerignore
in your current project
fly.toml
# fly.toml app configuration file generated for app-name on 2024-07-29T15:46:53-04:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = 'app-name'
primary_region = 'gru'
kill_timeout = '5m0s'
[build]
[[mounts]]
source = 'uploads'
destination = '/public/uploads'
initial_size = '1gb'
[http_service]
internal_port = 1337
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[services]]
protocol = 'tcp'
internal_port = 1337
processes = ['app']
[[services.ports]]
port = 80
handlers = ['http']
[[vm]]
cpu_kind = 'shared'
cpus = 1
memory_mb = 1024
Multi-stage dockerfile using Alpine (it will reduce the image 4.3GB → 1.1GB)
dockerfile
FROM node:18-alpine3.18 AS build_image
# Installing libvips-dev for sharp Compatability
RUN apk update && apk add --upgrade --no-cache vips-dev build-base fftw-dev gcc autoconf g++ make libc6-compat zlib-dev libpng-dev nasm bash --repository https://alpine.global.ssl.fastly.net/alpine/v3.18/community/
# Set working directory
WORKDIR /opt
# Resolve node_modules for caching
COPY package.json yarn.lock ./
# Install dependencies
RUN yarn install
# Copy all for build and release cache if package.json update
COPY . .
ENV NODE_ENV=production
RUN yarn build
# Remove unnecessary files from node_modules - https://github.com/tj/node-prune
RUN wget https://gobinaries.com/tj/node-prune --output-document - | /bin/sh && node-prune
#------------------------------------------------------------------------------------
# Create a new namespace for the final Docker Image
FROM node:18-alpine3.18
# Installing openssh
RUN apk --no-cache update && apk add openssh
# Installing vips-dev (https://www.libvips.org/) - https://github.com/strapi-community/strapi-plugin-local-image-sharp
RUN apk add vips-dev
# Set working directory
WORKDIR /opt/app
# Only copy your source code without the system file
COPY --from=build_image /opt ./
EXPOSE 1337
ENV NODE_ENV=production
ENV STRAPI_LOG_LEVEL=debug
CMD ["yarn", "start"]
.dockerignore
.tmp/
.cache/
.git/
build/
node_modules/
data/
Before launch, please copy and paste all environment variables in the .env
file to make it easier to deploy
fly launch
Wait until the deployment finishes.
Strapi data transfer
Strapi provides an API to transfer data (schema, content, etc.) between servers. It’s pretty handy! Thanks, Strapi team!
First, we have to access your Strapi Admin in the Fly.io and generate a token that enables PUSH data
Create the token with PUSH permission.
Save the token in a safe place.
Access your Render server and run the following command in the root folder of the Strapi project (~/project/src
). This command will send all data related to the database (entities and links) to your Fly.io server. It’s amazing! We will send the assets from the public folder later.
1. Unfortunately, I didn’t find a way to send the files (assets) because I received this error: [FATAL] restore failed Error: The backup folder for the assets could not be created inside the public folder. Please ensure Strapi has write permissions on the public directory
. This issue is caused by the folder linked to the volume not having permission to run some strapi scripts into it.
npm run strapi transfer -- --to https://app-name.fly.dev/admin --exclude files
Now paste the transfer token when you are asked.
Waiting until finish the transfer.
Later you can check in the Fly.io if the content is there.
Next, transfer all environment variables of .env
file for Secrets in Fly.io
Now we going to send the files (images)
Backup Render public folder
Access the server via SSH. How to set up your SSH connection.
ssh YOUR_SERVICE@ssh.YOUR_REGION.render.com
Compress the public
folder.
zip -r public.zip public
Close the SSH connection.
exit
You can transfer files using SCP. Copying a file from your service to your local machine
scp -s YOUR_SERVICE@ssh.YOUR_REGION.render.com:/path/to/remote/file /destination/path/for/local/file
scp -s srv-cbtddcr19n0c5dqs15i0@ssh.oregon.render.com:/opt/render/project/src/public.zip ./strapi/public.zip
Next, we must create a folder called public_
in your strapi root folder (locally). It will serve as a temporary folder to transfer the backup content. Now unzip the public.zip
into the folder.
mkdir public_
# Unzip, after creating a public folder into the public_
# We must move the content of the public_/public for public_
unzip public.zip -d public_
# Enter into the folder, move the content and delete the unnecessary folder
cd public_ && mv public/* . && rm -rf public
# It should contain only robots.txt and uploads folder
ls
Now your project contains two public folders (one is public_
is a temp folder).
Now run the deploy.
fly deploy
During the deployment, all folders from the project will be copied into the docker image so you can later access the VM and move the content.
Wait until the deployment finishes.
Now access via SSH the VM.
fly ssh console
Inside the VM, run the command below.
cp -r /opt/app/public_/. /opt/app/public
rm -rf /opt/app/public_
It will copy the content from public_
to public
folder and later delete the public_
.
I know it’s pretty verbose and complex, but we have permission to perform this kind of operation only via SSH.
💡 I hope the Fly.io team will enable us to send files for our volumes without running a machine using sftp
. I tried other alternatives using rsync
and wireguard
, which are the worst.
Check if the volume of the machine was increased (the default is 70MB). This is an indication that persisted in volume.
I hope this tutorial can help you migrate without pain.
Fly.io is one of the best money-saving options for anyone starting a small project. I no longer had any problems connecting to the bank; even with the reduction of infrastructure, there was no loss in user experience.
If you have any questions, please feel free to comment below.
Top comments (1)
Thanks for the recap; it has been of great help!
However, my use case was connecting a local Strapi instance to a remote one hosted on Fly.io. I had to handle proxying the remote database to establish a connection.
For some reason, I couldn’t use port 5432 and had to use 5433 instead:
flyctl proxy 5433 -a
Additionally, I used the wrong generated Strapi token and encountered unauthorized connection issues because I used the source Strapi one instead of the remote Strapi one 🙃.
Finally, I had to deal with a 'duplicate key value' error during the operation, which prevented a successful transfer. I had to implement some skipping logic to complete the entire migration.