logo

runway

A self-hosted deployment controller for anything

test lint acceptance build build release

language crystal platforms amd64 and arm64 Dockerized, oh yeah!


About 💡

Runway is deployment controller that runs on an event driven system. You define the events that should trigger deployments and then you configure how you want those deployments to be executed. Runway is not a CI/CD system, it is a deployment controller. It is meant to be run on a server that can reach the internet and can also reach your target servers or projects. It can run on the same server as your projects, or on a separate server. It is up to you how you want to configure it.

See the full project goals here for even more information about why this project was created.

Features 🚀

Quickstart ⭐

This section goes into a brief example of how you can use runway

Basic Example

Here is a very basic example of using runway:

First, create a runway configuration file (config.yml). This config file tells runway how to run.

# This is a runway configuration file
# config.yml
projects:
  - name: project-1
    deployment: # deployments are actions that get triggered by events
      type: command # this deployment type is a command that gets run
      location: local # the command type is 'local', so the command is executed on the machine by runway
      path: /home/bob/project-1/ # go to this path before running the command
      timeout: 5
      entrypoint: bash
      cmd: ["-c", "echo 'I did a cool deployment!'"] # add your own custom logic here to deploy project-1
    events: # events are actions that can trigger deployments
      - type: file
        path: deploy-it.txt # look for this file and if it's found, trigger the deployment
        cleanup: true # remove the file after the event
        schedule:
          interval: 3s # how often to check for the event

Now start runway:

runway -c config.yml

In this example, runway will check for the existence of the deploy-it.txt file in the current directory every 3 seconds. If this file is found, that is an event trigger. Event triggers kick off the logic defined under the deployment section for a given project.

So if we were to create the deploy-it.txt file, runway would execute the command deployment. The command deployment would go to the /home/bob/project-1/ directory on the local system and run bash -c echo 'I did a cool deployment!'. After the deployment is complete, the deploy-it.txt file gets cleaned up.

You can go ahead and test out this example for yourself to give runway a go!

Advanced Example

Here is a more complex example of using runway:

First, create a docker-compose.yml file which will be responsible for starting runway and mounting a volume that contains your runway configuratin file:

# example docker-compose.yml file
services:
  runway:
    container_name: runway
    restart: unless-stopped
    image: ghcr.io/runwaylab/runway:vX.X.X # <--- replace with the tag you want to use
    command: ["-c", "config/config.yml"]
    volumes:
      - keys:/app/keys
      - config:/app/config
    env_file:
      - creds.env

volumes:
  config:
    driver: local
    driver_opts:
      o: bind
      type: none
      device: ./config
  keys:
    driver: local
    driver_opts:
      o: bind
      type: none
      device: ./keys

Next, create a runway configuration file at config/config.yml relative to your docker-compose.yml file. This directory gets mounted into runway's container so that your config is accessible to runway.

# This is a runway configuration file
# config/config.yml
projects:
  - name: project-1
    deployment:
      type: command # this deployment type will run a command
      location: remote # the location will be "remote" so runway will attempt an SSH connection to the remote server
      remote:
        auth: publickey # the auth mode will be via a public/private key pair to the remote server (SSH)
        host: "192.168.1.5" # this is the host to SSH into - perhaps another host on your home network
        port: 22 # the port to use for SSH connections
        username: ubuntu # the username of the system you want to SSH to
        public_key_path: /app/keys/id_rsa.pub # the public key from the "keys" volume to use for public key auth
        private_key_path: /app/keys/id_rsa # the corresponding private key of the public/private key pair
        success_string: deployment-complete # the string to look for in the command's output to determine if the deployment was successful
      timeout: 30 # the total SSH / command timeout for execution
      entrypoint: bash # the entrypoint of the command to run on the remote host
      cmd: ["-c", "'script/deploy --ref={{ payload.ref }} && echo deployment-complete'"] # the actual command arguments to run
    events: # events that runway looks for to trigger a deployment
      - type: github_deployment # github deployment event
        repo: runwaylab/test-flight # the repository to check for deployment events
        environment: production # the specific environment to check for
        deployment_filter: 1 # only look at the most recent deployment (based on the created_at field) - this field is required only for the github_deployment type event. It helps to save on API requests to GitHub. If not provided, it defaults to 1
        schedule:
          interval: 15s # intervals can be in milliseconds, seconds or minutes (ms, s, or m) - or a cron expression
          timezone: UTC # the timezone to use for the schedule (default is UTC) - ex: Europe/Berlin or America/New_York - see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
      - type: github_deployment # here we define another event to look for, in this case we also check the staging environment
        repo: runwaylab/test-flight
        environment: staging
        deployment_filter: 1
        schedule:
          interval: 5s
          timezone: UTC

Now we need to create a keys/ directory that contains our public/private key pair that we need for runway to SSH into the remote host we defined above.

Please ensure the keys/ dir has 700 permissions and your public/private keys have 600 permissions

We also need to create a creds.env file which contains a GitHub PAT that is scoped to allow read access to deployments since our configuration is specifically looking for GitHub deployments. You may also need repo permissions on the token as well if your repository is private.

# creds.env
GITHUB_TOKEN=ghp_abcdefg

Now we can fire up runway!

docker compose up --build -d

Let's explain what this all did:

  1. We created a docker compose service to start runway
  2. We configured our docker compose service to mount a keys and a config volume from our local disk. The keys volume contains the public/private key pair used for remote SSH commands on a given server with public key authentication
  3. We created a new runway configuration file (under config/config.yml) giving runway events to listen for (in_progress GitHub deployments) and deployments to run when these events are triggered
  4. We created a new keys/ directory and placed our public/private keys inside of it (with the correct permisions)
  5. We created a new creds.env file containing our GITHUB_TOKEN so that runway can authenticate and listen for / complete GitHub deployments

Now if runway detects an in_progress deployment for the runwaylab/test-flight repository under either the production or staging environment, it will SSH into the remote server and execute the script/deploy script with the provided GitHub REF that triggered the deployment.

Note: Yes this example was complex and verbose. Yes this example requires some fine tuning and setup to work for your project... but that is the point, showing you what can be accomplished with runway and how you can leverage it for your own projects in a very flexible/open way!

Using the github_deployment deployment type makes runway the ultimate deployer of your application as far as GitHub deployments are concerned. In order to fully leverage this deployment type, you need to create deployments, and not complete them within GitHub Actions. This is because runway will look for them, run its defined deployment configuration, and then complete them for you!

To see a live example of how this works using github/branch-deploy and Actions, checkout this live example here

Event Types 🕐

Events are "things" that runway looks for which trigger deployments. Events can be anything, from the existence of a new file, to a new tag being published, to a GitHub deployment.

For a complete list of event types and how to use them, see the events documentation.

Deployment Types 🚀

Deployments are triggered by events and define how your "project" gets deployed. Deployments are generally commands that runway executes.

For a complete list of deployment types and how to use them, see the deployments documentation.

Contributing 🤝

See the contributing documentation to learn more about how you can contribute or develop runway.