Deployment
When you're ready to deploy your Formidable application to production, there are some important things you can do to make sure your application is running as efficiently as possible. In this document, we'll cover some great starting points for making sure your Formidable application is deployed properly.
Server Requirements
The Formidable framework has a few system requirements. You should ensure that your web server has the following minimum Node version:
Node >=18.*
npm/pnpm/yarn/bun
Deploy
Heroku (recommended)
Formidable is Heroku-ready out of the box. Here are some few things you may need to do to get started:
Create a Procfile
in the root of your application with the following content:
web: npm start
cron: node craftsman schedule:run
If your application is making use of the queue system, you can add the following line to your Procfile
:
worker: node craftsman queue:work
Don't forget to add production .env
details to Heroku. Remember to set APP_DEBUG
to false
.
That's all you need to do to get started.
Nginx
If you need more control over your server and application, we recommend deploying to a Linux server and using Nginx and PM2.
Before getting started, make sure the following prerequisites are met:
Serving Your Application
Now that you have all the dependencies, you can go ahead and create a ecosystem.config.js
file in the root of your application:
module.exports = {
apps: [
{
name: "web",
script: "npm run start",
time: true,
error_file: "./storage/logs/web/error.log",
out_file: "./storage/logs/web/log.log"
},
{
name: "cron",
script: "node craftsman schedule:run --no-ansi",
max_memory_restart: "100M",
time: true,
error_file: "./storage/logs/cron/error.log",
out_file: "./storage/logs/cron/log.log"
}
]
}
And finally, start your application:
pm2 start ecosystem.config.js
By default, this will start our application on http://127.0.0.1:3000
, we can change port in the server
file:
Server
.use(require('./.formidable/build').default)
.start({
port: 3000,
host: '127.0.0.1'
})
We also recommend you enable PM2 to auto start your application on system boot. You can do this by running the command: pm2 startup
Creating a Reverse Proxy
Now that you have started your application you can go ahead and create a virtual host:
server {
listen 80;
server_name _;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.0;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Once thats done, we can check for any issues on our newly created app.conf
:
sudo nginx -t
If everything is fine, we should see a "success" message. Then we can enable our application by creating a symbolic link of the app.conf
file from the /etc/nginx/sites-available/
directory to /etc/nginx/sites-enabled/
:
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
Now, for changes to reflect, you will need to restart Nginx:
sudo systemctl reload nginx
And now you should be able to access your application 🎉🎉🎉
Automating things
Its not always practical to ssh into your server to pull your latest changes. Because of this, we may wish to automate things a bit using Bash scripts.
Here's a simple bash script that pulls the latest changes from a repo and run the necessary commands:
- npm
- pnpm
- yarn
- bun
echo "Jump to application folder"
cd /root/app
echo "Update application from Git"
git pull
echo "Install application dependencies"
npm install
echo "Build application"
npm run build
echo "Put application in maintenance mode"
node craftsman down
echo "Run database migrations"
node craftsman migrate:latest --no-interaction
echo "Restart application"
pm2 restart ecosystem.config.js
echo "Put application back online"
node craftsman up
echo "Jump to application folder"
cd /root/app
echo "Update application from Git"
git pull
echo "Install application dependencies"
pnpm install
echo "Build application"
pnpm run build
echo "Put application in maintenance mode"
node craftsman down
echo "Run database migrations"
node craftsman migrate:latest --no-interaction
echo "Restart application"
pm2 restart ecosystem.config.js
echo "Put application back online"
node craftsman up
echo "Jump to application folder"
cd /root/app
echo "Update application from Git"
git pull
echo "Install application dependencies"
yarn install
echo "Build application"
yarn run build
echo "Put application in maintenance mode"
node craftsman down
echo "Run database migrations"
node craftsman migrate:latest --no-interaction
echo "Restart application"
pm2 restart ecosystem.config.js
echo "Put application back online"
node craftsman up
echo "Jump to application folder"
cd /root/app
echo "Update application from Git"
git pull
echo "Install application dependencies"
bun install
echo "Build application"
bun run build
echo "Put application in maintenance mode"
node craftsman down
echo "Run database migrations"
node craftsman migrate:latest --no-interaction
echo "Restart application"
pm2 restart ecosystem.config.js
echo "Put application back online"
node craftsman up
This script can be triggered by a Github Action, for example. When we push to our main
branch, we can have a Github Workflow that ssh's into our server on our behalf and executes the deploy.sh
script:
name: Deploying
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: executing remote ssh commands using ssh key
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
key: ${{ secrets.KEY }}
script: sh /root/deploy.sh
This is only an example. You don't have to use Github to be able to Automate your deployments. The same can also be achieved with Gitlab and Bitbucket Pipelines.
Docker
You can also use Docker to deploy your application. Here's a simple Dockerfile
that you can use to build your application:
FROM node:18-alpine
# Create app directory
RUN mkdir -p /usr/app
WORKDIR /usr/app
# Install app dependencies
COPY package.json /usr/app/
RUN npm install
# Bundle app source
COPY . /usr/app
RUN npm run build
COPY . /usr/app
EXPOSE 3000
CMD ["npm", "start"]
Set APP_DEBUG
to false in your .env
file before building your application for production.
Vercel
Vercel is another great option for deploying your Formidable application. While Formidable is not officially supported by Vercel, you can still deploy your application to Vercel by following these steps:
Configure your application
First, you need to trust a couple of dependencies by adding them to your package.json
file:
{
...
"trustedDependencies": [
"bcrypt",
"esbuild",
"sqlite3"
],
...
}
Then create a vercel.json
file in the root of your application:
{
"buildCommand": "bun run build",
"installCommand": "bun install",
"outputDirectory": ".formidable/public",
"devCommand": "bun run dev",
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/serverless.js"
}
]
}
You can also use
npm
oryarn
orpnpm
instead ofbun
. However, we recommend using thebun
package manager in this case.
Create a serverless function
Next, we need to create a serverless function that will serve as our entry point. Create a serverless.js
file in the api
directory:
const formidable = require('../.formidable/build').default
export default async (req, res) => {
const application = await formidable
const app = application.fastify()
await app.ready()
app.server.emit('request', req, res)
}
Your serverless function must be named serverless.js
and must be located in the api
directory.
Deploy
Finally, you can deploy your application to Vercel by running the following command:
vercel
You can find the instructions for installing the Vercel CLI here.
Considerations
While this approach works, there are some things you need to consider:
Logging
By default, Formidable logs to the storage/logs
directory. However, Vercel does not allow writing to the file system. So, you will need to use the console
channel instead. You can change the default log channel in the .env
file:
LOG_CHANNEL=console
Database
Vercel supports Formidable's pgsql
driver. So, you can use Postgres as your database. However, you will need to use Vercel's Postgres service. Once you have the credentials, you can add them to your .env
file:
DB_CONNECTION=pgsql
DATABASE_URL=postgres://default:**********@**********.eu-central-1.postgres.vercel-storage.com:5432/verceldb?sslmode=require
Don't forget to add ?sslmode=require
to your DATABASE_URL
.
And finally, ensure that you have the pgsql
driver installed:
bun add pgsql
Redis
Vercel only supports the memory
and redis
session drivers. So, we recommend using the memory
driver for your application if you do not have a redis server.
You can however use Vercel KV. Once you have the credentials, you can add them to your .env
file:
REDIS_URL=redis://default:**********@**********.kv.vercel-storage.com:32857
Once that's done, you can use the redis
driver for your application:
- Imba
- TypeScript
export default {
# --------------------------------------------------------------------------
# Default Session Driver
# --------------------------------------------------------------------------
#
# This option controls the default session "driver" that will be used on
# requests. By default, we will use the lightweight native driver but
# you may specify any of the other wonderful drivers provided here.
#
# Supported: "memory", "file", "redis"
#
# See: "bootstrap > resolvers.imba"
driver: 'redis'
...
}
export default {
/**
* --------------------------------------------------------------------------
* Default Session Driver
* --------------------------------------------------------------------------
*
* This option controls the default session "driver" that will be used on
* requests. By default, we will use the lightweight native driver but
* you may specify any of the other wonderful drivers provided here.
*
* Supported: "memory", "file", "redis"
*
* See: "bootstrap > resolvers.ts"
*/
driver: 'redis',
...
The next step would be to update your redis' default connection in the config/database.{imba,ts}
file:
- Imba
- TypeScript
export default {
...
# --------------------------------------------------------------------------
# Redis Databases
# --------------------------------------------------------------------------
#
# You can configure your redis databases here.
redis: {
default: {
url: helpers.env('REDIS_URL')
database: helpers.env('REDIS_DB', '0')
tls: true
}
}
}
export default {
...
/**
* --------------------------------------------------------------------------
* Redis Databases
* --------------------------------------------------------------------------
*
* You can configure your redis databases here.
*/
redis: {
default: {
url: helpers.env('REDIS_URL'),
database: helpers.env('REDIS_DB', '0'),
tls: true
}
}
}
In the default redis
connection, we set tls
to true
. This is because Vercel KV uses TLS. And we removed the host
, port
and password
options because they are not needed.
Package Lock
Please ensure that you remove your package-lock.json
, yarn.lock
, pnpm-lock.yaml
or bun.lockb
file before deploying to Vercel. You may encounter some issues if you don't.