- Explore creating a custom executor
- Go through an example of how to deploy an API to Fly.io through Nx
-
For this workshop you'll need two CLI tools installed:
-
Let's prepare Fly to deploy our API:
# login first fly auth login # Get an authorization token so we don't have to login everytime fly auth token
⚠️ Make sure you remember and keep track of the authorization token, as we'll use it later. -
Let's setup our ENV variables from the beginning now
apps/api/.local.envFLY_API_TOKEN=<your-fly-token>
-
Create a new file
apps/api/src/fly.tomlPick a unique app name to include in the
fly.tomlfile.👉 This will determine the address where the API will be deployed to:
https://<your-app-name>.fly.devapp = "<your-unique-app-name>" kill_signal = "SIGINT" kill_timeout = 5 processes = [] [build] builder = "paketobuildpacks/builder:base" buildpacks = ["gcr.io/paketo-buildpacks/nodejs"] [env] PORT = "8080" [experimental] cmd = ["PORT=8080 node main.js"] [[services]] http_checks = [] internal_port = 8080 processes = ["app"] protocol = "tcp" script_checks = [] [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] force_https = true handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s"❓ What's our plan here?
Fly will launch a pre-build node Docker image (or you could provide your own) and then run the command you specify to launch the server.
So the plan is:
- define a
fly.tomlwith instructions for fly to deploy the server - when we want to deploy, we'll build our app to
dist/apps/api - as part of the build, we need to make sure that our
fly.tomlfile makes it intodist/apps/api - Fly will copy the bundled code to the remote server and run the node server via
cmd = ["PORT=8080 node main.js"]
- define a
-
If you
nx build apiright now- 👍 Then
cd dist/apps/api && node main.jsIt should work. Because it has access tonode_modules - 👎 If you copy your built sources to some other folder on your file system.
And then try to
node main.jsin that folder that doesn't hace access tonode_modules- it will fail
💡 By default, dependencies of server projects are not bundled together, as opposed to your Angular apps. If curious why, you can read more here.
- 👍 Then
-
Let's fix the above - In
project.json, under the production build options for the API (targets -> build -> configurations -> production) add this as an option:"externalDependencies": [ "@nestjs/microservices", "@nestjs/microservices/microservices-module", "@nestjs/websockets/socket-module", "class-transformer", "class-validator", "cache-manager", "cache-manager/package.json" ],
❓ What does this do?
The above option tells webpack to bundle ALL the dependencies our API requires inside
main.js, except the ones above (which fail the build if we tell webpack to include, because they're lazily loaded). Normally, it's not recommended to bundle any dependencies with your server bundles, but in this case it simplifies the deployment process.
-
Currently the
fly.tomlthat we added to ourapiproject is not present if we inspect thedist/apps/apidirectory after running a prod build. We'll need this to be present for our fly deployment.Update the the
assetsoption in the production build options for the API (targets -> build -> configurations -> production)"assets": [ "apps/api/src/assets", "apps/api/src/fly.toml" ],
-
Use the
@nx/plugin:executorgenerator to generate afly-deployexecutor:- The executor should have options for:
- the target
distlocation - the
nameof your fly app
- the target
- When running, your executor should perform the following tasks, using the
flycli:- list the current fly apps:
fly apps list - if the app doesn't exist, launch it:
fly launch --now --name=<the name of your Fly App> --region=lax - if the app does exist, deploy it again:
fly deploy
- list the current fly apps:
Fly launch and deploy commands need to be run in the
distlocation of your app.Use the
@nx/plugin:executorto generator an executor in ourinternal-pluginproject for this:npx nx generate @nx/plugin:executor fly-deploy --project=internal-plugin
- The executor should have options for:
-
Adjust the generated
schema.jsonandschema.d.tsfile to match the required options:{ "$schema": "http://json-schema.org/schema", "cli": "nx", "title": "FlyDeploy executor", "description": "", "type": "object", "properties": { "distLocation": { "type": "string" }, "flyAppName": { "type": "string" } }, "required": ["distLocation", "flyAppName"] }export interface FlyDeployExecutorSchema { distLocation: string; flyAppName: string; }
-
Implement the required fly steps using
execSyncto call theflycli inside yourexecutor.tsfile:import { FlyDeployExecutorSchema } from './schema'; import { execSync } from 'child_process'; export default async function runExecutor( options: FlyDeployExecutorSchema ) { const cwd = options.distLocation; const results = execSync(`fly apps list`); if (results.toString().includes(options.flyAppName)) { execSync(`fly deploy`, { cwd }); } else { // consult https://fly.io/docs/reference/regions/ to get best region for you execSync(`fly launch --now --name=${options.flyAppName} --region=lax`, { cwd, }); } return { success: true, }; }
-
Next we'll need to add a
deploytarget to ourapps/api/project.jsonfile (don't forget to put your apps name inflayAppNamefield):{ "deploy": { "executor": "@bg-hoard/internal-plugin:fly-deploy", "outputs": [], "options": { "distLocation": "dist/apps/api", "flyAppName": "my-unique-app-name" }, "dependsOn": [ { "target": "build", "projects": "self", "params": "forward" } ] } } -
Let's enable CORS on the server so our API can make requests to it (since they'll be deployed in separate places):
-
In
apps/api/src/main.ts -
Enable CORS:
async function bootstrap() { const app = await NestFactory.create(AppModule); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); app.enableCors(); // <--- ADD THIS }
⚠️ Normally, you want to restrict this to just a few origins. But to keep things simple in this workshop we'll enable it for all origins.
-
-
Now run the command to deploy your api!!
npx nx deploy api --prod
Because of how we set up our
dependsOnfor thedeploytarget, Nx will know that it needs to run (or pull from the cache if you already ran it) the production build of the api before then running the deploy! -
Go to
https://<your-app-name>.fly.dev/api/games- it should return you a list of games.⚠️ Since we are on a free tier, it might take some time for application to become available
-
BONUS - What would a meaningful test be for your new executor? Add it to
libs/internal-plugin/src/executors/fly-deploy/executors.spec.ts
🎓If you get stuck, check out the solution