View on GitHub
Full source for this example in fal-serverless-examples.
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
- Clone the examples repository:
- Create a Python 3.11 environment:
- Install the pinned SDK and test runner:
requirements.txt contains the SDK pin:
requirements.txt
- Run the local endpoint tests:
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. TheCICDDemo 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
AppClient tests.
AppClient creates a remote deployment, so this credential-free job does not use it.
Preparing GitHub and fal
- 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 bothpathsfilters anddefaults.run.working-directoryin the workflow to match. - Create a fal
stagingenvironment with the Manage Environments guide. - Create a fal key with the
DEPLOYpreset. Follow Key-Based Authentication for key creation and permissions. - In GitHub, open Settings > Environments and create two environments named
stagingandproduction. - In each GitHub environment, add an environment secret named
FAL_DEPLOY_KEYwith the fal key as its value. Do not store it as a repository secret. - In the
productionenvironment, set Deployment branches and tags to allow only themainbranch. - In both environments, add required reviewers before you do step 8. Reviewers on
stagingare the only gate for pull request deployments. - When you are ready to deploy, open Settings > Secrets and variables > Actions > Variables and add a repository variable named
FAL_CI_CD_ENABLEDwith the exact valuetrue.
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.
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
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
- Open a pull request that changes the example folder or workflow.
- In GitHub Actions, confirm that
Test apppasses. For a same-repository pull request,Deploy appthen waits for approval. - Approve the deployment if the pull request is trusted.
- After deployment, send a request to the staging app. Staging URLs end in
--staging. Replaceyour-userwith your fal username or team name. SetFAL_KEYto a separate key with theAPIpreset from the same account or team. Do not use theDEPLOYkey for requests.
{"message":"Hello, fal!"}. You can also see the app in the Apps dashboard.
For more checks, see Test Models and Endpoints.
- Merge the reviewed change into
mainto trigger the production deployment. The production URL has no suffix:https://fal.run/your-user/ci-cd-demo.
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 targetmain explicitly:
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 sameFAL_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.