Skip to main content

View on GitHub

Full source for this example in fal-serverless-examples.
This example connects local endpoint tests to a fal deployment with GitHub Actions. The app runs on a CPU and returns a greeting. It needs no model weights or GPU. When you finish, each matching pull request runs local tests. After approval, a same-repository pull request deploys ci-cd-demo to fal staging. A merge to main deploys it to fal main, which this example uses for production. The workflow tests each matching pull request without credentials. Deployment stays off until you set a repository variable, FAL_CI_CD_ENABLED, to true.

Running the Example

  1. Clone the examples repository:
  1. Create a Python 3.11 environment:
  1. Install the pinned SDK and test runner:
The example’s requirements.txt contains the SDK pin:
requirements.txt
  1. Run the local endpoint tests:
The output ends with 3 passed. These tests do not need a fal key or create remote infrastructure. The deployment workflow runs the same command before it can deploy.

Defining the App

The request contains a name. The response contains a greeting. The CICDDemo class exposes one endpoint at / and requires authentication for deployed requests.
app.py
machine_type = "S" selects a CPU machine. app_auth = "private" requires a fal key for each request. keep_alive=60 keeps a runner warm for 60 seconds after the last request. The SDK pin matches requirements.txt so the local and remote environments use the same version. The workflow’s --app-name ci-cd-demo flag gives the app the same name in each fal environment.

Testing Before Deployment

These tests call the endpoint method directly. They check a greeting and reject missing or non-string names. _allow_init=True opts into local construction for this stateless unit test. The SDK blocks normal construction outside a fal runner. The file loader selects this example’s app.py explicitly, avoiding other examples with the same module name. In your own repository, from app import CICDDemo, Input works instead.
test_app.py
These tests do not verify container builds, remote startup, or authenticated requests. See Test Models and Endpoints for local server checks and live AppClient tests. AppClient creates a remote deployment, so this credential-free job does not use it.

Preparing GitHub and fal

  1. Copy the 01-environments-containers/deploy-from-ci-cd/ folder into your GitHub repository at the same path. You add the workflow file in the next section. If you put the app at a different path, change both paths filters and defaults.run.working-directory in the workflow to match.
  2. Create a fal staging environment with the Manage Environments guide.
  3. Create a fal key with the DEPLOY preset. Follow Key-Based Authentication for key creation and permissions.
  4. In GitHub, open Settings > Environments and create two environments named staging and production.
  5. In each GitHub environment, add an environment secret named FAL_DEPLOY_KEY with the fal key as its value. Do not store it as a repository secret.
  6. In the production environment, set Deployment branches and tags to allow only the main branch.
  7. In both environments, add required reviewers before you do step 8. Reviewers on staging are the only gate for pull request deployments.
  8. When you are ready to deploy, open Settings > Secrets and variables > Actions > Variables and add a repository variable named FAL_CI_CD_ENABLED with the exact value true.
Until you set FAL_CI_CD_ENABLED to true, the workflow runs tests and shows the Deploy app job as skipped. This is expected. On private repositories, GitHub plan limits apply. Environment secrets need GitHub Pro, Team, or Enterprise. Required reviewers need GitHub Enterprise. If your plan cannot provide these gates, do not set FAL_CI_CD_ENABLED. Do not use a repository secret instead. GitHub environments control the workflow’s approvals and secrets. They are separate from fal environments. The GitHub environment production targets fal’s main environment, not a fal environment named production. The workflow does not configure approval policies for you. Every job can read a repository secret. A pull request that edits the workflow can then remove the environment block and skip its approvals. For this reason, the key goes in environment secrets only.
Key scope: the DEPLOY preset can change apps, secrets, and environments. The key is not limited to one fal environment.Same-repository pull requests: code in these pull requests can use the key during deployment. Treat that code and its authors as trusted.Staging can reach production: a pull request author can edit the workflow to deploy through the staging GitHub environment with --env main. The production branch rule does not apply to that path.
  • Review workflow changes in the pull request before you approve a staging deployment.
  • If staging deployments must never reach production, deploy staging to a separate fal team with its own key.
Forks and Dependabot: fork pull requests only run tests and never receive the key. GitHub withholds Actions secrets from Dependabot pull requests, so the workflow skips deployment for them.Never change this workflow to use pull_request_target to deploy pull request code.
The workflow exposes the secret as FAL_KEY only in the deploy step. The CLI uses it for noninteractive authentication. Do not run fal auth login in CI.

Adding the Workflow

Place this file at .github/workflows/deploy.yml in the repository root. GitHub does not discover workflows inside the example folder.
.github/workflows/deploy.yml
The table at the top of this page describes the deploy job’s if condition. The deploy job depends on the test job. A test failure prevents deployment. The fal CLI comes from the pinned SDK in requirements.txt. Each run step runs in the example folder because of defaults.run.working-directory. Both jobs check out the event’s revision. For pull requests, this is GitHub’s merge revision. The workflow pins both actions to commit SHAs and disables persistent checkout credentials. The deploy command selects the environment explicitly. --strategy rolling selects a rolling deployment. --reset-scale applies the scaling settings in app.py, such as keep_alive, on every deploy. Without it, a redeploy keeps the previous revision’s scaling. With it, scaling changes made in the dashboard are overwritten by the next deploy. --attach keeps the CLI attached to the deployment. --yes skips interactive confirmation. See Deploy to Production for rollout behavior. The deployment concurrency group separates staging from main. Deployments to the same target cannot run simultaneously. cancel-in-progress: false lets an active deployment finish when another run arrives. GitHub concurrency does not guarantee deployment order or preserve every pending run.

Using the Pipeline

  1. Open a pull request that changes the example folder or workflow.
  2. In GitHub Actions, confirm that Test app passes. For a same-repository pull request, Deploy app then waits for approval.
  3. Approve the deployment if the pull request is trusted.
  4. After deployment, send a request to the staging app. Staging URLs end in --staging. Replace your-user with your fal username or team name. Set FAL_KEY to a separate key with the API preset from the same account or team. Do not use the DEPLOY key for requests.
The response is {"message":"Hello, fal!"}. You can also see the app in the Apps dashboard. For more checks, see Test Models and Endpoints.
  1. Merge the reviewed change into main to trigger the production deployment. The production URL has no suffix: https://fal.run/your-user/ci-cd-demo.
Staging is shared, not a separate preview for each pull request. The next successful staging deployment replaces the code from the previous one. A merge deploys the tested main revision independently. It does not promote the staging app. To make Test app a required check in branch protection, remove the paths filter under pull_request. Otherwise, pull requests that do not touch these paths wait for a check that never runs. After you remove it, every same-repository pull request requests a staging deployment. For a manual staging run, select Actions > deploy-ci-cd-demo > Run workflow in GitHub. The workflow must exist on the default branch for this control to appear. Manual runs always target staging, even when you select main as the source branch. They still require passing tests, deployment opt-in, a configured key, and any environment approvals.

Rolling Back a Deployment

Use Rollbacks and Revisions to inspect revisions and select a previous version. For this example’s production app, the commands target main explicitly:
Replace REVISION_ID with the revision you choose from the list. A rollback changes the live app, not the repository. Revert or fix the source before the next automated deployment.

Other CI Providers

The same FAL_KEY environment variable and CLI steps work in GitLab CI and CircleCI. Store the deployment key in the provider’s secret store. Run the local tests before the deploy command. Keep the same trusted-code restrictions and explicit --env staging or --env main selection.