Browse documentation

Custom build commands and CI deploys

Change what runs on every GitHub deploy, add your own build steps, and deploy only code that passed your tests in GitHub Actions.

CanerAkar
On this page

Every GitHub deploy on FPS.ms runs a deploy script: a short Bash script that fetches your commit and installs your packages before the server restarts. You can add your own build steps to it on the Script tab, for example npm run build or a database migration. To deploy only code that passed its tests, let GitHub Actions push to the branch your server deploys, or call the CI webhook URL from your workflow. This guide assumes you already deploy from GitHub.

Run your Python script for free

What the default script does

Every runtime starts with a script that works for most projects. It fetches the commit you pushed, then runs the install or build step below. When it finishes, the server restarts and starts your app.

Runtime What the default script runs
Python pip install from requirements.txt
Node.js npm ci when there is a package-lock.json, otherwise npm install
Bun bun install
.NET dotnet publish on your .csproj
Dart dart pub get, then dart compile exe
Go go build
Java mvn package with a pom.xml, otherwise a Gradle build
Kotlin A Gradle build, or kotlinc when there is no Gradle project
Rust cargo install from your crate
Haskell cabal build, or stack build when there is no .cabal file
Lua luarocks install for your .rockspec and extra rocks
Odin odin build

Add your own build steps

  1. Click Manage on your server, then More and Deployments.
  2. Click Script. The editor shows your server's deploy script.
  3. Add your commands at the end. Keep the lines that fetch the commit at the top.
  4. Click Save. The new script is used from the next deploy.

For example, a Node.js app that compiles TypeScript ends its script like this:

Deploy script
# Install dependencies.
if [ -f package-lock.json ]; then
    npm ci --no-audit --no-fund
elif [ -f package.json ]; then
    npm install --no-audit --no-fund
fi

# Compile TypeScript into dist/
npm run build

And a Django app can run its migrations on every deploy:

Deploy script
python manage.py migrate
python manage.py collectstatic --noinput

The script stops at the first command that fails, and the deploy is marked as failed. The build log shows which line it was.

Back to the default

Click Reset to egg default to throw away your changes and use the runtime's default script again.

Variables you can use in the script

Variable What it holds
$RELEASE_DIR The folder the deploy builds in: your server's main folder
$COMMIT_SHA The commit being deployed
$BRANCH The branch that was pushed
$DEPLOYMENT_ID A unique ID for this deploy

Your environment variables are available in the script too, for example an API key a build step needs.

Deploy only code that passed its tests

A push to the connected branch deploys right away. To deploy only tested code, let your tests decide what reaches that branch:

  1. Connect your server to a branch that only your workflow writes to, for example production.
  2. Run your tests on every push to main in GitHub Actions.
  3. When they pass, the workflow pushes the commit to production, and FPS.ms deploys it.
.github/workflows/deploy.yml
name: Test and deploy

on:
  push:
    branches: [main]

permissions:
  contents: write

jobs:
  test-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - run: python -m pytest
      - name: Deploy tested code
        run: git push origin HEAD:production

If the push does not start a deploy, add the webhook call from the next section after the git push step.

Start a deploy from CI with the webhook URL

Every connected server also has a CI webhook URL. Calling it starts a deploy of the newest commit on the connected branch, or of the commit you name. Use it when a deploy should wait for something else, like a release you publish by hand.

  1. On the Deployments tab, click Settings.
  2. Under CI webhook URL, copy the Webhook URL.
  3. Click Rotate deploy token and copy the token. It is only shown once.
  4. In GitHub, open your repository's Settings, then Secrets and variables and Actions. Add two secrets: FPS_DEPLOY_URL with the URL and FPS_DEPLOY_TOKEN with the token.
  5. Add a step that calls the URL:
Deploy step
      - name: Deploy to FPS.ms
        run: |
          curl --fail -X POST "$FPS_DEPLOY_URL" \
            -H "Authorization: Bearer $FPS_DEPLOY_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"commit_sha\": \"$GITHUB_SHA\"}"
        env:
          FPS_DEPLOY_URL: ${{ secrets.FPS_DEPLOY_URL }}
          FPS_DEPLOY_TOKEN: ${{ secrets.FPS_DEPLOY_TOKEN }}

Leave out the -d line to deploy the newest commit on the branch.

Auto-deploy must be on

The webhook URL only works while Auto-deploy is on, so pushes to the connected branch deploy too. If you want CI to be the only way in, connect a branch nobody pushes to by hand.

If the call fails, the answer says why:

Answer Meaning
401 invalid_token or missing_token The token is wrong, revoked or not sent
409 auto_deploy_disabled Turn Auto-deploy on
422 server_not_bound No repository is connected to the server
429 deploy_rate_limited Too many deploys in a short time; wait and try again

To stop old tokens from working, click Rotate deploy token again or Revoke token.

Next steps

Run your Python script or bot for free.

Upload your code, add your environment variables and keep it running 24/7.

Run your script free