# Step-by-Step Guide to Deploying Strapi on Fly.io

## Strapi

[Strapi](https://strapi.io) is a free, open-source content management system (CMS) built with Node.js. It is a "headless" CMS, meaning it doesn't come with a pre-built front-end user interface. Instead, Strapi offers a REST API and several SDKs that let you access your content using any front-end framework you choose.

## Fly.io

[Fly.io](https://fly.io) is a cloud platform designed for developers. Fly machines are hardware-virtualized containers that run on Fly's infrastructure. They can be launched instantly and run for as long as you need them. These machines can start quickly enough to handle a single API request or be set up to run for weeks.

The simplest and quickest way to deploy to a Fly.io machine is to have a Dockerfile in your project. This allows Fly.io to build an image for your project and then deploy that image to Fly.

## Strapi installation

We’re going to first install Strapi onto our local machine. You *will* need Docker to follow along with the rest of this tutorial. I’m on a Macbook, so I use [Docker desktop](https://www.docker.com/products/docker-desktop/).

#### Database

Strapi supports the following databases and their respective versions:

| **Database** | **Recommended** | **Minimum** |
| --- | --- | --- |
| MySQL | 8.0 | 8.0 |
| MariaDB | 10.6 | 10.5 |
| PostgreSQL | 14.0 | 12.0 |
| SQLite | 3 | 3 |

<div data-node-type="callout">
<div data-node-type="callout-emoji">🤚</div>
<div data-node-type="callout-text">Strapi does not support MongoDB</div>
</div>

I’m a Postgres person, so let’s get started with that. Once you have Docker Desktop installed and running, open up a terminal and run:

```bash
docker run --name strapi-database -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:14
```

This will start a Postgres container on port 5432.

#### Create a new Strapi project

```bash
npx create-strapi@latest
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1728896702156/65e85b67-8bb0-4f6c-9669-aa8ee4cea3d7.png align="center")

You will be asked several questions after you run this command. For this tutorial, you should select `Skip` for the cloud account creation, choose `postgres` as your database, set the DB name to `postgres` and I chose the username `postgres` and password `postgres` to keep things simple.

#### Running Strapi for development

To start the Strapi application:

```bash
npm run develop
```

The Strapi application will open a browser tab at `http://localhost:1337`. You’ll be asked to fill out a form which will be the creation of your first admin account within Strapi.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">In development mode, Strapi provides access to the content-type builder. However, in production mode, you cannot create or modify collections or content types from the admin panel. Instead, all content types and collections are stored as code and assembled during the build process. Therefore, to add new collections or content types, you must run Strapi in development mode and then re-deploy to production to make the new collections and content types available.</div>
</div>

## Strapi and Docker

Strapi does not provide any “official” Docker images, but they do supply Dockerfiles in their docs so that you can create your own images.

Create a `Dockerfile.prod` file within the root of your Strapi project.

```bash
touch Dockerfile.prod
```

Here’s a Dockerfile for a production environment:

```dockerfile
# ./Dockerfile.prod

# Creating multi-stage build for production
FROM node:18-alpine as build
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git > /dev/null 2>&1
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install -g node-gyp
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production
ENV PATH=/opt/node_modules/.bin:$PATH
WORKDIR /opt/app
COPY . .
RUN npm run build

# Creating final production image
FROM node:18-alpine
RUN apk add --no-cache vips-dev
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY --from=build /opt/node_modules ./node_modules
WORKDIR /opt/app
COPY --from=build /opt/app ./
ENV PATH=/opt/node_modules/.bin:$PATH

RUN chown -R node:node /opt/app
USER node
EXPOSE 1337
CMD ["npm", "run", "start"]
```

We’ll use this Dockerfile later when we deploy to Fly.io

## Fly.io setup

#### Create a Fy.io account

If you don’t have one already, create an account at [Fly.io](https://fly.io/app/sign-up).

#### Install the flyctl CLI on your machine

The official flyctl docs can be found [here](https://fly.io/docs/flyctl/install/).

I’m on a Mac so I used homebrew to install the CLI

```bash
brew install flyctl
```

From within your Strapi project root, run the following command:

```bash
flyctl launch --build-only
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The <code>—-build-only</code> flag ensures that we don’t deploy this app immediately after creation. We need to provide Fly.io with secrets (env variables) before deploying.</div>
</div>

You’ll be asked if you want to tweak the settings before proceeding. Choose `y`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1728898136525/5725d34f-88b1-4631-b9bc-b82f01905453.png align="center")

You’ll need a 2GB machine *minimum* for the image to build. Fly will also detect that you need Postgres. This is the easiest way to get Postgres connected to your Fly app. Fly.io will automatically add the correct Postgres connection variables to your secrets. Here’s my configuration:

![configure your fly.io app to have at least 2GB of memory](https://cdn.hashnode.com/res/hashnode/image/upload/v1728898249163/02ff5965-3a67-41b2-b55a-dc2321f28f6a.png align="center")

A `fly.toml` file will be created in your file system. We need to modify this by adding a line under the `http_service` config. This will tell Fly.io that our Strapi app needs to run on port 1337 internally.

![change the internal port to 1337](https://cdn.hashnode.com/res/hashnode/image/upload/v1728900003917/6049f2e1-ba9d-4070-8e99-99671d1a49d5.png align="center")

After you confirm these settings, Fly.io will build your image and set up a machine/app for you. You’ll then be able to see your app within the dashboard.

![fly.io dashboard view with app and database present](https://cdn.hashnode.com/res/hashnode/image/upload/v1728898485341/60a4131a-008c-44bf-a4f8-36e080328736.png align="center")

Select your app from the list in the dashboard (not the database). Then select `Secrets`.

![select secrets from the menu](https://cdn.hashnode.com/res/hashnode/image/upload/v1728898681004/5829d018-b2f5-4e13-9ca9-c80615152bd8.png align="center")

**Copy all secrets** from your `.env` file in your Strapi project to secrets. Of course, change any of them that you’d like to be “prod” vs “dev” secrets.

![copy your .env variables to secrets](https://cdn.hashnode.com/res/hashnode/image/upload/v1728898828230/95e20043-be45-4c7f-b0ed-231e51f2c7d8.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Notice the yellow dot next to your newly added secrets. This is telling you that your app needs to be redeployed in order for the secrets to be considered “built” into your Strapi image.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Missing in the image above is <code>DATABASE_CLIENT</code>. This needs to be set to inform Strapi as to which database you’re using.</div>
</div>

Now you should be able to deploy your Strapi image to Fly.io.

```bash
flyctl deploy --dockerfile Dockerfile.prod
```

Watch Fly.io build your Docker image.

![fly.io building the Docker image](https://cdn.hashnode.com/res/hashnode/image/upload/v1728899122851/d733ca5b-9f80-4d44-b19a-4449fa0412f0.png align="center")

Once finished, Fly.io will inform you that your app is available at the specified URL.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1728900179887/0ebf80aa-f99c-4f50-943f-c0a84a387442.png align="center")

Visit the provided link and you’ll see your Strapi app is live on the internet!

![Production Strapi dashboard.](https://cdn.hashnode.com/res/hashnode/image/upload/v1728900221204/7c923a73-796d-4e94-a773-5646eaa83f94.png align="center")

Congrats! You’ve deployed Strapi to fly.io

If you have any questions, or if I’ve missed anything, please leave a comment. Also, please subscribe so you don’t miss out on any updates.

Get the code for this project here:

[https://github.com/rawestmoreland/strapi-v5-docker-postgres](https://github.com/rawestmoreland/strapi-v5-docker-postgres)
