How I used dev containers to enable GitHub Codespaces for ChatGPT
Tags: chatgpt, codespaces, github, cloud
Published: 2023-05-08 18:47:19

OpenAI recently launched a beta feature called ChatGPT plugins. Essentially, this feature enables third-party tools to interact with the GPT model. To assist developers in creating their own plugins for ChatGPT, OpenAI introduced a quickstart repository as a guide. To further enhance accessibility, OpenAI's Developer Advocate, Logan Kilpatrick, contacted technologists who work with browser-based or cloud-based Integrated Development Environments (IDEs). Logan's goal was to enable developers to use the quickstart repository locally or with browser-based IDEs like CodeSandbox and Replit. In doing so, developers could build and experiment with plugins directly in their browsers, eliminating the need to clone the repository. Fortunately, one of the cloud-based environments on the list was GitHub Codespaces. Logan reached out to me about his goal, and I was thrilled to accept the challenge.

I had three main reasons for my excitement about adding GitHub Codespaces to the quickstart repository:

* I would gain access to ChatGPT plugins, which I imagine have a never-ending waitlist.
* I would gain experience configuring GitHub Codespaces in unfamiliar environments. My experience with GitHub Codespaces is limited to JavaScript-based projects.
* I'm a nerd.

Last week, I officially submitted a pull request to enable GitHub Codespaces for the project. The Codespaces team has already reviewed it, and I'm now waiting for the OpenAI team to do the same. In the meantime, I'm sharing what I've learned throughout this experience in the hope that you can learn more about GitHub Codespaces too!


### Prerequisites:

_This is not necessarily a tutorial that you should follow step by step, but if you did want to follow it, you would need access to the following tools._

* ChatGPT account
* ChatGPT plugins
* GitHub account 
* It might be helpful to understand the purpose of GitHub Codespaces. You can learn about it from my previous [blog post](https://dev.to/github/why-are-people-developing-inside-containers-38o6). 


### A short summary of GitHub Codespaces

GitHub Codespaces allows you to code in a container hosted in the cloud. This helps developers onboard faster, code on any device, and code in a consistent environment. The biggest benefit I see for this particular ChatGPT quickstart plugin repository is that developers can try out the plugin functionality without the hassle of cloning the project locally or running any setup scripts. They can try it out without context switching and leaving GitHub. And best of all, they won’t have to do any of the setup. Note: you can open any project in GitHub Codespaces. The UI for GitHub Codespaces can resemble your favorite IDE – like Visual Studio Code or JetBrains, but it’s just in your browser. You can also configure GitHub Codespaces to install all the dependencies and run the project to reduce the time it takes for developers to start working on a project.

Here are the steps I took and the lessons I learned while setting up GitHub Codespaces for this project:


## Step 1: Get the project working locally

The first question I needed to answer before configuring GitHub Codespaces was: _how does this project work? What is the end result? _

I found my answer by following the local setup instructions found in the repository’s README.md. The instructions were simple: install the required packages and then run the project. Subsequently, the server will run locally on `localhost:5003`.

In ChatGPT, I chose the ‘develop your own plugin’ option.

![ChatGPT plugin store](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brbc6opkzec09whzvwkv.png)

As instructed, I provided ChatGPT with the local address that the server was running on – localhost:5003. 

![enter your website domain on ChatGPT’s interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/euinsd95tjlvjyfpkcim.png)

After all that, I was able to leverage the plugin to create a simple to do list with ChatGPT.

![me creating a to do list with ChatGPT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwdjyuji5zwxv4u61ox0.png)

If you want to try it out, check out the README [here](https://github.com/openai/plugins-quickstart#readme). 

Throughout this process, I noted that there's no frontend for this project, so I only had one goal—when anyone opens this project in a codespace, the server should run.


## Step 2: Open the repository in GitHub Codespaces

This step ensures that any changes I make are actually compatible with GitHub Codespaces. The code from the repository will be accessible in GitHub Codespaces out of the box, but it won't start running my project or installing dependencies without configuration. So, the next step is to add a configuration file called a devcontainer.json.


## Step 3: Add a dev container

Development containers, fondly known as dev containers, are Docker containers that set up a customized environment for the project. I can use a dev container to automatically:



* Install the necessary extensions
* Reference environment variables
* Forward ports
* Install dependencies
* Run my project
* Run scripts
* And more 

You can add a default development container by opening the Visual Studio Code Command Palette. This default configuration file contains the basic items you need to get a project running. To open the Visual Studio Code Command Palette, use the following keyboard shortcuts: (Shift+Command+P / Ctrl+Shift+P). Then, search for the option to "Add dev container configuration files."


![Command Palette that says Add Dev container configuration files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtr1fl303hn5pamrsl6d.png)


It will prompt you to specify your project's language because each development container is slightly different depending on the language, framework, version, and other environmental factors.  For this case, the project is Python-based, so we would choose Python.

![Command Palette suggesting a Python 3 devcontainer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfge696zp3s3jk7wcpmg.png)

Unfortunately, I did not follow the advice of adding a development container through the Command Palette. Instead, at the root of my project, I created a folder called .devcontainer. Inside that folder, I created a file called devcontainer.json. I copied and pasted a development container from a past Python project into the devcontainer.json. I did this because the example development container included examples of how to leverage the postAttachCommand and postCreateCommand. However, this was a big mistake that I don't recommend because I ended up copying and pasting different lines that I thought were arbitrary, but they impacted the project, and I had to delete those lines.

My recommendation is to use the command palette to add a barebones development container, and then customize it from there.

Here's what our boilerplate devcontainer.json file could look like:

My recommendation is to use the command palette to add a barebones development container, and then customize it from there.

Here’s what our boilerplate `devcontainer.json` file could look like: 

```json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the

// README at: https://github.com/devcontainers/templates/tree/main/src/python

{

  "name": "Python 3",

  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

  "image": "mcr.microsoft.com/devcontainers/python:0-3.11-bullseye",

  "features": {

    "ghcr.io/devcontainers-contrib/features/coverage-py:2": {}

  }

  // Features to add to the dev container. More info: https://containers.dev/features.

  // "features": {},

  // Use 'forwardPorts' to make a list of ports inside the container available locally.

  // "forwardPorts": [],

  // Use 'postCreateCommand' to run commands after the container is created.

  // "postCreateCommand": "pip3 install --user -r requirements.txt",

  // Configure tool-specific properties.

  // "customizations": {},

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

  // "remoteUser": "root"

}

```


## Step 4: Customize the dev container

At this point, the repository now has a development container, but it still doesn’t run or install anything because this development container is just boilerplate, and it has zero knowledge of what my project needs to run automatically.


### Install dependencies 

Based on the local set up, one of the first steps for this project is to install dependencies by running this command:

```pip install -r requirements.txt```

I needed to make GitHub Codespaces run this command, so the developer wouldn’t have to manually run it. I added this line to the `postCreateCommand`. The `postCreateCommand` is responsible for running commands after the container is created. The line looked like this:

```json

postCreateCommand: pip install -r requirements.txt

```

So now, my development container looked like this:

```json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the

// README at: https://github.com/devcontainers/templates/tree/main/src/python

{

  "name": "Python 3",

  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

  "image": "mcr.microsoft.com/devcontainers/python:0-3.11-bullseye",

  "features": {

    "ghcr.io/devcontainers-contrib/features/coverage-py:2": {}

  }

  // Features to add to the dev container. More info: https://containers.dev/features.

  // "features": {},

  // Use 'forwardPorts' to make a list of ports inside the container available locally.

  // "forwardPorts": [],

  // Use 'postCreateCommand' to run commands after the container is created.

  "postCreateCommand": "pip install -r requirements.txt",

  // Configure tool-specific properties.

  // "customizations": {},

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

  // "remoteUser": "root"

}

```


### Port Forwarding

In the dev container, there's an option to define a list of ports where the container is available locally. From the local setup, I saw that the preferred port is localhost:5003, so I added that to the array of forwardedPorts in my devcontainer.json file.

Here’s what the line looked like:

```json

"forwardPorts": [5003],

```

Here’s what my `devcontainer.json` file looked like:

```json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the

// README at: https://github.com/devcontainers/templates/tree/main/src/python

{

  "name": "Python 3",

  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

  "image": "mcr.microsoft.com/devcontainers/python:0-3.11-bullseye",

  "features": {

    "ghcr.io/devcontainers-contrib/features/coverage-py:2": {}

  }

  // Features to add to the dev container. More info: https://containers.dev/features.

  // "features": {},

  // Use 'forwardPorts' to make a list of ports inside the container available locally.

  "forwardPorts": [5003],

  // Use 'postCreateCommand' to run commands after the container is created.

  "postCreateCommand": "pip install -r requirements.txt",

  // Configure tool-specific properties.

  // "customizations": {},

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

  // "remoteUser": "root"

}

```


### Run the project

To locally run the project, developers have to manually run: python main.py, but I wanted GitHub Codespaces to do this instead. I used the postAttachCommand property in my devcontainer.json file to run this command.

The postAttachCommand enables scripts to run in the terminal after the client connects to the codespace. Here's the line I added:

```json

"postAttachCommand": “python main.py”

```

Here’s what my `devcontainer.json` file looked like:

```json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the

// README at: https://github.com/devcontainers/templates/tree/main/src/python

{

  "name": "Python 3",

  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

  "image": "mcr.microsoft.com/devcontainers/python:0-3.11-bullseye",

  "features": {

    "ghcr.io/devcontainers-contrib/features/coverage-py:2": {}

  }

  // Features to add to the dev container. More info: https://containers.dev/features.

  // "features": {},

  // Use 'forwardPorts' to make a list of ports inside the container available locally.

  "forwardPorts": [5003],

  // Use 'postCreateCommand' to run commands after the container is created.

  "postCreateCommand": "pip install -r requirements.txt",

  “postAttachCommand”: “python main.py”

  // Configure tool-specific properties.

  // "customizations": {},

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

  // "remoteUser": "root"

}

```


## Step 5: Test by rebuilding the container 

I made all these changes to my development container, but I need to check if it actually works, so I rebuilt my container via the Visual Studio Code command palette and chose the option to "Rebuild container."

![Command Palette suggesting rebuild container](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3xy709fxi9z6fbkilnut.png)


After the rebuild was completed, I saw that the Codespace automatically installed the required packages and ran the server on localhost:5003. It also forwarded the port for me to a randomly generated URL that looked like something like:

`https://USERNAME-CODESPACE-NAME-vrpqrxxrx7x2rxx-5003.preview.app.github.dev`

I copied and pasted the randomly generated URL into ChatGPT, but I received error messages that ChatGPT couldn't find the manifest files nor could it install the plugin.


## Step 6: Debugging


### Port visibility

I learned that one reason ChatGPT couldn’t find the manifest was because my forwarded port was private, so ChatGPT didn’t have access to the code. Fortunately, I can manually change the port visibility by right clicking the port and switching the visibility from “private” to “public”. 



![shows the option to make your port public](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7im8g7113kjjb0svb6a.png)


I checked to see if there was a way to have GitHub Codespaces set this port to public automatically. However, after I spoke with the GitHub Codespaces, I learned there was no easy way to do this, and it could cause a potential security breach, so I decided users could manually do this. 


### Updating the files

Another issue I came across was that there were two files that specified the URL localhost:5003. The names of the files were `openapi.yaml` and `.well-known/ai-plugin.json`. However, my new URL was different. It now resembled a randomly generated URL that looked like this: `https://USERNAME-CODESPACE-NAME-vrpqrxxrx7x2rxx-5003.preview.app.github.dev`.

I felt like I was at an impasse and I had two options:



* I could make developers manually update those files to match their newly generated port. They would just have to follow the directions in the README.md. My problem with this option is that it makes GitHub Codespaces feel clunky. Users already have to update the port visibility and _now_ they have to update the files. (Boo, tomato, tomato, tomato). I wanted to represent GitHub Codespaces in its best light.
* OR I could edit the source code, and replace the words localhost:5003 with something like `https://${CODESPACE_NAME}-5003.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}`. However, that would ruin the experience for developers who choose to still develop the plugin locally. 

I reached out to the Codespaces team for help. They advised me to write a script that dynamically updates the url in those particular files if the repository is opened in a Codespace. The script I wrote looks like this:

```bash

#!/bin/bash

set -e

# Determine the value of SITE_HOST based on whether the project is opened in a Codespace

if [ -n "$CODESPACE_NAME" ]; then

        SITE_HOST="https://${CODESPACE_NAME}-5003.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"

        # Replace "localhost:5003" with the value of SITE_HOST in the ai-plugin.json file

        sed -i "s#http://localhost:5003#${SITE_HOST}#g" .well-known/ai-plugin.json

        # Replace "localhost:5003" with the value of SITE_HOST in the openapi.yaml file

        sed -i "s#http://localhost:5003#${SITE_HOST}#g" openapi.yaml

fi

``` 

Instead of having the developer manually run this script, I made my codespace run it automatically by adding the following to my devcontainer.json file:

```json

"postAttachCommand": ".devcontainer/addcodespacename.sh && python main.py",

```

Now, before the program runs, the codespace will run this shell script and update the files to use the correct URL.

Here’s what my devcontainer.json looked like:

```json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the

// README at: https://github.com/devcontainers/templates/tree/main/src/python

{

  "name": "Python 3",

  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

  "image": "mcr.microsoft.com/devcontainers/python:0-3.11-bullseye",

  "features": {

    "ghcr.io/devcontainers-contrib/features/coverage-py:2": {}

  }

  // Features to add to the dev container. More info: https://containers.dev/features.

  // "features": {},

  // Use 'forwardPorts' to make a list of ports inside the container available locally.

  "forwardPorts": [5003],

  // Use 'postCreateCommand' to run commands after the container is created.

  "postCreateCommand": "pip install -r requirements.txt",

  "postAttachCommand": ".devcontainer/addcodespacename.sh && python main.py",

  // Configure tool-specific properties.

  // "customizations": {},

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

  // "remoteUser": "root"

}

```

I rebuilt the container for testing, and everything worked as expected!


## Step 7: Nice-to-haves


### Opening important files post-Codespace creation

This is not required, but I thought it would be convenient to have the two most important files opened after the codespace is created. Because this is a new project and just a new concept, I wanted to make it easy for people to navigate the project. And I know there’s a property in a `devcontainer.json` called `openFiles` that handles that!

Here are the lines I added:

```json

	"customizations": {

		"codespaces": {

			"openFiles": [

				".well-known/ai-plugin.json",

				"openapi.yaml"

			]

		}

	}

}

``` 

Here is what my `devcontainer.json` file looked like at this point:

```json

// For format details, see https://aka.ms/devcontainer.json. For config options, see the

// README at: https://github.com/devcontainers/templates/tree/main/src/python

{

  "name": "Python 3",

  // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

  "image": "mcr.microsoft.com/devcontainers/python:0-3.11-bullseye",

  "features": {

    "ghcr.io/devcontainers-contrib/features/coverage-py:2": {}

  }

  // Features to add to the dev container. More info: https://containers.dev/features.

  // "features": {},

  // Use 'forwardPorts' to make a list of ports inside the container available locally.

  "forwardPorts": [5003],

  // Use 'postCreateCommand' to run commands after the container is created.

  "postCreateCommand": "pip install -r requirements.txt",

  "postAttachCommand": ".devcontainer/addcodespacename.sh && python main.py",

 // Configure tool-specific properties.

  "customizations": {

		"codespaces": {

			"openFiles": [

				".well-known/ai-plugin.json",

				"openapi.yaml"

			]

		}

	}

}

  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

  // "remoteUser": "root"

}

```


## Step 8: Clean up 

Everything is working exactly how I wanted, and now I just wanted to clean up any unused properties and confusing comments. 

Here’s what my final devcontainer.json file looks like:

```json

// For format details, see https://aka.ms/devcontainer.json.

{

	"name": "ChatGPT Quickstart Plugins",

	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile

	"image": "mcr.microsoft.com/devcontainers/python:0-3.11",

	// Use 'forwardPorts' to make a list of ports inside the container available locally.

	"forwardPorts": [

		5003

	],

	// Use 'postCreateCommand' to run commands after the container is created.

	"postCreateCommand": "pip install -r requirements.txt",

	"postAttachCommand": ".devcontainer/addcodespacename.sh && python main.py",

	"customizations": {

		"codespaces": {f

			"openFiles": [

				".well-known/ai-plugin.json",

				"openapi.yaml"

			]

		}

	}

}

```


## What’s next?

I’ll wait for the OpenAI team to either approve or suggest changes to my [pull request](https://github.com/openai/plugins-quickstart/pull/14). I’m looking forward to seeing it get merged! I'd love to contribute more dev containers to open source projects for folks who want to improve their projects' onboarding experience and enable them to work on their projects within GitHub Codespaces.

_If you have questions or any cool things you learned about development containers or GitHub Codespaces, comment below!_