NGINX.COM
Web Server Load Balancing with NGINX Plus

Software settings, options, and configurations exist in many different forms. At one end of the spectrum are the slick, responsive GUIs meant to be intuitive while providing guardrails against invalid states. On the other end: text files. While text files are lauded by both engineers and DevOps teams for their clarity, automation potential, and minimal usage requirements (you likely have a few open terminal windows, right?), there are clear trade-offs to configuring software with them. For example, try to find a Linux user who hasn’t managed to crash a software package by misconfiguring a text file.

As the only all-in-one software proxy, load balancer, web server and API gateway, NGINX is a key component of modern internet infrastructure. An infrastructure that is, in most cases, based on operating systems underpinned by the Linux kernel. To conform to this ecosystem and the professionals supporting it, NGINX relies heavily on text-based configurations.

The F5 NGINX Instance Manager module has long been the go-to for NGINX-related config orchestration. It provides advanced capabilities for remote, batch configuration management through its intuitive user interface and API, with supporting documentation and guardrails to boot. However, individual NGINX configuration files strongly resemble code and software teams already have an amazing tool for managing code: Git. Git provides developers and operations teams a slew of features geared towards managing text file workflows.

Instance Manager’s new integration with Git and other external systems enables features like version control, decentralized contribution, approval workflows, and team coordination. Platforms like GitHub and GitLab extend this feature set to continuous integration/continuous deployment (CI/CD), collaboration, issue tracking, and other valuable functions through a web-based user interface.

In this blog post we illustrate how GitHub can be used to manage NGINX configurations, automatically pushing them to instances whenever a change is made.

Instance Manager API

Instance Manager provides a rich set of REST APIs that complement its web user interface. A key benefit to the API is its ability to update configuration files of data plane instances under management. Recently, we extended this functionality by enabling the ability to tie configuration updates to a specific version of the file. When managed in a Git repository, configurations can be tagged with a Git commit hash. Additionally, we implemented a new state within Instance Manager for externally managed configs that warns would-be file editors that configurations are under external management.

GitHub Actions

GitHub allows developers to create custom deployment pipelines in their repositories using a feature called Actions. A user may choose to define their own actions or invoke existing scripts via a YAML definition. These pipelines can be triggered in a variety of ways, like when a repository is updated with a commit or a pull request merge.

In this blog’s example, we lean on out-of-the-box GitHub Actions and Linux commands. You will learn to use them to update GitHub-managed NGINX configuration files on your NGINX instances via the Instance Manager API. To start, follow these steps on GitHub Docs to create a new YAML for running Actions in your repository.

Setting Actions Secrets

Instance Manager supports various forms of authentication. In the example, we use the Basic Authentication method, though we recommend provisioning OIDC authentication in production environments.

Rather than storing Instance Manager credentials in the repository, GitHub allows you to define secrets as repository variables. These variables are accessible by the Actions environment but hidden in logs. Follow these steps to store Instance Manager username and password keys as secrets so you can use them to authenticate your API calls.

Here, we’ve set these variables to NMS_USERNAME and NMS_PASSWORD.

Screenshot of a tech platform displaying repository secrets options

Setting Actions Variables

Similarly, rather than defining constant variables in your YAML, it can be helpful to factor them out for management in the GitHub user interface. On the Variables page, you can find instructions on how to define variables that span all repository Actions. In the example, we use this opportunity to define the Instance Manager FQDN or IP (NMS_HOSTNAME), identifier of the system NGINX is running on (SYSTEM_ID), and identifier of the specific NGINX instance to be updated (INSTANCE_ID).

Screenshot of a tech platform displaying repository variables options

Note: We’ve set these variables to simplify our example, but you may choose to manage Instance Manager, System, and NGINX identifying information in other ways. For instance, you may opt to create directories in your repository containing configurations specific to different instances and name these directories with system or instance IDs. You could then modify your YAML or Action script to read directory names and update configuration files on the corresponding instance.

Anatomy of the Instance Manager REST API Call for Updating NGINX Configurations

The Instance Manager configuration update REST API call requires several key components to work. Your YAML will need to define each of these parameters and package them into the API call in the proper format.

In the example, we use the API call for updating a single instance. However, it’s also possible to configure an instance group within Instance Manager. Doing so enables you to update all instances in a group whenever a new configuration is pushed from GitHub. For more information, please see our How-To Guide on publishing configurations.

Below is a breakdown of Instance Manager’s configuration update REST API call:


https://{INSTANCE MANAGER HOSTNAME}/api/platform/v1/systems/{SYSTEM ID}/instances/{INSTANCE ID}/config' 
--header "accept: application/json" 
--header "Authorization: Basic {LOGIN CREDENTIALS}" 
--header 'Content-Type: application/json'
--data '{
    "configFiles": '{
        "rootDir": "/etc/nginx",
        "files": [{
            "contents": "{NGINX CONFIGURATION FILE}",
            "name": "{PATH TO CONFIGURATION FILE ON SYSTEM}"
        }]
    },
    "externalIdType": "{SOURCE OF CONFIGS}",
    "externalId": "{COMMIT HASH}",
    "updateTime": "{TIMESTAMP}"
}'}'

URI Parameters

  • System identifier – A unique key identifying the system that NGINX is running on. In our example, this data is defined in the GitHub Actions Variables interface.
  • NGINX instance identifier – A unique key identifying a specific NGINX instance on the system. In our example, this data is defined in the GitHub Actions Variables interface.

Header Parameters

  • Authorization – The authorization method and Base64 encoded credentials that Instance Manager uses to authenticate the API call. In the example, you will use Basic Authorization with credentials data coming from the secrets set in the repository.

JSON Data Parameters

  • configFiles – The Base64 encoded contents of configuration files being updated, along with their locations on the system running NGINX. You will also need to provide a path to the root directory of your NGINX configuration files, most commonly configured as: /etc/nginx.
  • externalIdType – A label that identifies the source of the configuration file to Instance Manager. Possible values are git or other. In our example, we hardcode this parameter to git.
  • externalId – The Git commit Secure Hash Algorithm (SHA) that identifies the change to the configuration files.
  • updateTime – A string containing the current date and time in %Y-%m-%dT%H:%M:%SZ format.

Base64 Encoding

To accommodate Instance Manager’s API specification, you must transform certain data by encoding it into Base64 format. While there isn’t a native way to accomplish this with existing GitHub Actions, we can rely on Linux tools, accessible from our YAML.

Instance Manager Credentials

Start by referencing the Instance Manager login credentials that were defined earlier as secrets and concatenate them. Then, convert the string to Base64, echo it as a Linux variable (NMS_LOGIN) and append the result to a predefined environment variable (GITHUB_ENV), accessible by the Actions runner.

run: echo "NMS_LOGIN=`echo -n "${{ secrets.NMS_USERNAME }}:${{ secrets.NMS_PASSWORD }}" | base64`" >> $GITHUB_ENV

Timestamp

The Instance Manager API requires that a specifically formatted timestamp is sent with certain API payloads. You can construct the timestamp in that format using the Linux date command. Similar to the previous example, append the constructed string as a variable to the Linux environment.

run: echo "NMS_TIMESTAMP=`date -u +"%Y-%m-%dT%H:%M:%SZ"`" >> $GITHUB_ENV

NGINX Configurations

Next, add the NGINX configurations that you plan to manage into the repository. There are many ways of adding files to a GitHub repository. For more information, follow this guide in GitHub’s documentation. To follow our example, you can create a directory structure in your GitHub repository that mirrors that of the instance.

The YAML entry below reads the configuration file from your repository, encodes its contents to Base64 and adds the result to an environment variable, as before.

run: echo "NGINX_CONF_CONFIG_FILE=`cat nginx-server/etc/nginx/nginx.conf | base64 -w 0`" >> $GITHUB_ENV

In our example, we repeat this for every configuration file in our GitHub repository.

Putting It All Together

Finally, you can use GitHub’s sample reference implementation to piece together what you’ve learned into a working YAML file. As defined in the file, all associated GitHub Actions scripts will run whenever a user updates the repository through a commit or pull request. The final entry in the YAML will run a curl command that will make the appropriate API call, containing the necessary data for Instance Manager to update all the related configuration files.

Note: Use the multi-line run entry (run: |) in your YAML to run the curl command because this instructs the YAML interpreter to treat colons “:” as text in the parameter portion of the entry.


name: Managing NGINX configs with GitHub and GitHub Actions 
# Controls when the workflow will run 
on: 
  # Triggers the workflow on push or pull request events but only for the "main" branch 
  push: 
    branches: [ "main" ] 
  pull_request: 
    branches: [ "main" ] 
  
  # Allows you to run this workflow manually from the Actions tab 
  workflow_dispatch: 

# A workflow run is made up of one or more jobs that can run sequentially or in parallel 
jobs: 
  # This workflow contains a single job called "build" 
  build: 
    # The type of runner that the job will run on 
    runs-on: ubuntu-latest 

    # Steps represent a sequence of tasks that will be executed as part of the job 
    steps: 
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 
      - uses: actions/checkout@v4 

      - name: Set environment variable for NMS API login credentials 
        run: echo "NMS_LOGIN=`echo -n "${{ secrets.NMS_USERNAME }}:${{ secrets.NMS_PASSWORD }}" | base64`" >> $GITHUB_ENV 

      - name: Set environment variable for NMS API timestamp 
        run: echo "NMS_TIMESTAMP=`date -u +"%Y-%m-%dT%H:%M:%SZ"`" >> $GITHUB_ENV 

      - name: Set environment variable for base64 encoded config file 
        run: echo "NGINX_CONF_CONFIG_FILE=`cat app-sfo-01/etc/nginx/nginx.conf | base64 -w 0`" >> $GITHUB_ENV 

      - name: Set environment variable for base64 encoded config file 
        run: echo "MIME_TYPES_CONFIG_FILE=`cat app-sfo-01/etc/nginx/mime.types | base64 -w 0`" >> $GITHUB_ENV 

      - name: Set environment variable for base64 encoded config file 
        run: echo "DEFAULT_CONF_CONFIG_FILE=`cat app-sfo-01/etc/nginx/conf.d/default.conf | base64 -w 0`" >> $GITHUB_ENV 

      - name: Set environment variable for base64 encoded config file 
        run: echo "SSL_CONF_CONFIG_FILE=`cat app-sfo-01/etc/nginx/conf.d/ssl.conf | base64 -w 0`" >> $GITHUB_ENV 

      - name: API call to Instance Manager 
        run: | 
          curl --location 'https://${{ vars.NMS_HOSTNAME }}/api/platform/v1/systems/${{ vars.SYSTEM_ID }}/instances/${{ vars.INSTANCE_ID }}/config' --header "accept: application/json" --header "Authorization: Basic ${{ env.NMS_LOGIN }}" --header 'Content-Type: application/json' --data '{"configFiles": {"rootDir": "/etc/nginx","files": [{"contents": "${{ env.NGINX_CONF_CONFIG_FILE }}","name": "/etc/nginx/nginx.conf"},{"contents": "${{ env.MIME_TYPES_CONFIG_FILE }}","name": "/etc/nginx/mime.types"},{"contents": "${{ env.DEFAULT_CONF_CONFIG_FILE }}","name": "/etc/nginx/conf.d/default.conf"},{"contents": "${{ env.SSL_CONF_CONFIG_FILE }}","name": "/etc/nginx/conf.d/ssl.conf"}]},"externalIdType": "git","externalId": "${{ github.sha }}","updateTime": "${{ env.NMS_TIMESTAMP }}"}' 

NGINX Reference Implementation

Executing a configuration update API call after a file has changed can be achieved in different ways. While GitHub Actions is the most convenient method for those who use GitHub, it will not work for GitLab or standalone Git implementations. To address these use cases, we’ve developed a companion shell script reference implementation that can be triggered from the command line or invoked from custom scripts.

In conclusion, our new extension to the Instance Manager API provides a powerful tool for managing configuration updates, rollbacks, and version histories in a modern, decentralized way. Coupling the extension with a third-party text file and code management platform like GitHub enables additional workflow, CI/CD, collaboration, and issue tracking features through an intuitive web-based user interface.

We’d love to hear your thoughts! Give it a try and let us know what you think in the comments or by joining our NGINX Community Slack channel.

Hero image

Learn how to deploy, configure, manage, secure, and monitor your Kubernetes Ingress controller with NGINX to deliver apps and APIs on-premises and in the cloud.



About The Author

Michael Vernik

Michael Vernik

Sr Product Manager

About F5 NGINX

F5, Inc. is the company behind NGINX, the popular open source project. We offer a suite of technologies for developing and delivering modern applications. Together with F5, our combined solution bridges the gap between NetOps and DevOps, with multi-cloud application services that span from code to customer.

Learn more at nginx.com or join the conversation by following @nginx on Twitter.