Copilot CLI

Published: Oct 2, 2025 by Isaac Johnson

First, I keep hearing that Github just released Github CLI which is not true. The Github CLI has been around for a while which provides the gh CLI tool.

What is new is the Github Copilot CLI which rolled out in Public Preview just last week.

Let’s give it a try and see how it compares to a lot of these other Agentic AI CLIs we’ve been using

Installation

We should start with the Github Repo for the tool for the latest installation steps.

Like some of our newer MCP servers, this one requires NodeJS 22 or higher.

Let’s install the LTS version (jod)

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest$ nvm install lts/jod
Downloading and installing node v22.20.0...
Downloading https://nodejs.org/dist/v22.20.0/node-v22.20.0-linux-x64.tar.xz...
##################################################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v22.20.0 (npm v10.9.3)
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest$ nvm use lts/jod
Now using node v22.20.0 (npm v10.9.3)

I can then install the latest Copilot CLI

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest$ npm install -g @github/copilot

added 5 packages in 2s
npm notice
npm notice New major version of npm available! 10.9.3 -> 11.6.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.6.1
npm notice To update run: npm install -g npm@11.6.1
npm notice

On first launch it already detected my user id and asked if it could mess in this folder

/content/images/2025/09/copilot-01.png

I’m going to start with a simple ReactJS app request:

I stopped it because it pretty much went out to lunch on the thinking part and never returned.

When I did shut down, I did not see any details about tokens used or time as I’ve grown accustomed to with Claude, Gemini and Coder

/content/images/2025/09/copilot-02.png

I literally went to bed with the prompt going expecting to come back to see what the timeout error would be. Instead, when I returned in the morning it had kept going. Granted, I am in the free tier, so I’m not expecting fast results, but I really did think it was hung

/content/images/2025/09/copilot-03.png

Let’s give it a try

I really should have looked in my features beforehand as I don’t know what percentage was my front, but I can see my free tier for now is at 4% completions and %72 chat

/content/images/2025/09/copilot-05.png

Let’s just do an update and see how that goes

As we saw, the first time, the portions were off

/content/images/2025/09/copilot-07.png

But that was corrected in the second pass

/content/images/2025/09/copilot-08.png

That seems to have cost me “4%” of my allotment, circling back on the settings page

/content/images/2025/09/copilot-09.png

Compared to Github Copilot in VS Code

Let’s do some updates now in the Copilot window of VS Code.

That used up 2% in the Agentic mode.

Let’s do a test to see if the Dockerfile works

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ docker build -t dancing-beetle-app:latest .
[+] Building 55.4s (18/18) FINISHED                                                                           docker:default
 => [internal] load build definition from Dockerfile                                                                    0.0s
 => => transferring dockerfile: 570B                                                                                    0.0s
 => [internal] load metadata for docker.io/library/node:18-alpine                                                       0.6s
 => [internal] load metadata for docker.io/library/nginx:stable-alpine                                                  1.1s
 => [auth] library/nginx:pull token for registry-1.docker.io                                                            0.0s
 => [auth] library/node:pull token for registry-1.docker.io                                                             0.0s
 => [internal] load .dockerignore                                                                                       0.0s
 => => transferring context: 2B                                                                                         0.0s
 => [internal] load build context                                                                                       7.8s
 => => transferring context: 288.07MB                                                                                   7.7s
 => [build 1/6] FROM docker.io/library/node:18-alpine@sha256:8d6421d663b4c28fd3ebc498332f249011d118945588d0a35cb9bc4b8  0.0s
 => [stage-1 1/4] FROM docker.io/library/nginx:stable-alpine@sha256:8f2bcf97c473dfe311e79a510ee540ee02e28ce1e6a64e1ef8  2.4s
 => => resolve docker.io/library/nginx:stable-alpine@sha256:8f2bcf97c473dfe311e79a510ee540ee02e28ce1e6a64e1ef89bfad325  0.0s
 => => sha256:8f2bcf97c473dfe311e79a510ee540ee02e28ce1e6a64e1ef89bfad32574ef10 10.32kB / 10.32kB                        0.0s
 => => sha256:a97d82f709e2e0ef35e48a697aec860e12cbf2a0ffbfd95d7701976e81d470ed 10.77kB / 10.77kB                        0.0s
 => => sha256:0368fd46e3c6d237d81390ff086f93aee216df5cfa814041a491453fb0932a12 3.64MB / 3.64MB                          0.3s
 => => sha256:07273e8eb118b64e6d2c1e7e0f7404566bde3901e22871f42b68422c317904c7 2.49kB / 2.49kB                          0.0s
 => => sha256:669998b1fdf4ad752e6d1b278e90c3634ca699b0394fc3fe7342e72d404223d1 1.79MB / 1.79MB                          0.4s
 => => sha256:95be0f4718a48e45cacbaf6e19f7aed48101465aca88a577bd84a57ee42eba1b 628B / 628B                              0.3s
 => => sha256:62c31b33012d33aa0a2fe15793525a9d6d6bf9c57f1f9ec22b88a02f869b1c3e 954B / 954B                              0.4s
 => => extracting sha256:0368fd46e3c6d237d81390ff086f93aee216df5cfa814041a491453fb0932a12                               0.2s
 => => sha256:85a0985bea3a96919f3334aeec7cec165a06ffc948c8de0418026c0d516c7016 404B / 404B                              0.4s
 => => sha256:8485f215491f7b3eccbb9a4c66ebb932c076fa9d17aa985155b6c57e132c2042 1.21kB / 1.21kB                          0.5s
 => => sha256:e585282f42862920f33200111c879a44e860b5848afce1246fdcd321700641d0 15.51MB / 15.51MB                        1.1s
 => => sha256:f2d0d37ae31d4ab6192eeea9aaf8c1c83664db1db36cb22c98fd2fd259c1542c 1.40kB / 1.40kB                          0.6s
 => => extracting sha256:669998b1fdf4ad752e6d1b278e90c3634ca699b0394fc3fe7342e72d404223d1                               0.2s
 => => extracting sha256:95be0f4718a48e45cacbaf6e19f7aed48101465aca88a577bd84a57ee42eba1b                               0.0s
 => => extracting sha256:62c31b33012d33aa0a2fe15793525a9d6d6bf9c57f1f9ec22b88a02f869b1c3e                               0.0s
 => => extracting sha256:85a0985bea3a96919f3334aeec7cec165a06ffc948c8de0418026c0d516c7016                               0.0s
 => => extracting sha256:8485f215491f7b3eccbb9a4c66ebb932c076fa9d17aa985155b6c57e132c2042                               0.0s
 => => extracting sha256:f2d0d37ae31d4ab6192eeea9aaf8c1c83664db1db36cb22c98fd2fd259c1542c                               0.0s
 => => extracting sha256:e585282f42862920f33200111c879a44e860b5848afce1246fdcd321700641d0                               0.7s
 => CACHED [build 2/6] WORKDIR /app                                                                                     0.0s
 => [build 3/6] COPY package.json package-lock.json* ./                                                                 1.3s
 => [build 4/6] RUN npm ci --silent                                                                                    22.9s
 => [build 5/6] COPY . .                                                                                               11.2s
 => [build 6/6] RUN npm run build                                                                                       9.4s
 => [stage-1 2/4] COPY --from=build /app/build /usr/share/nginx/html                                                    0.0s
 => [stage-1 3/4] RUN rm /etc/nginx/conf.d/default.conf                                                                 0.2s
 => [stage-1 4/4] COPY nginx.conf /etc/nginx/conf.d/default.conf                                                        0.0s
 => exporting to image                                                                                                  0.1s
 => => exporting layers                                                                                                 0.0s
 => => writing image sha256:2aeaaad37973bd82b39cbd7600de6309198d310eebeb51ea7243fd52cf5d3d82                            0.0s
 => => naming to docker.io/library/dancing-beetle-app:latest                                                            0.0s
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ docker run -p 8080:80 --rm dancing-beetle-app:latest
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/09/30 10:49:49 [notice] 1#1: using the "epoll" event method
2025/09/30 10:49:49 [notice] 1#1: nginx/1.28.0
2025/09/30 10:49:49 [notice] 1#1: built by gcc 14.2.0 (Alpine 14.2.0)
2025/09/30 10:49:49 [notice] 1#1: OS: Linux 6.6.87.2-microsoft-standard-WSL2
2025/09/30 10:49:49 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2025/09/30 10:49:49 [notice] 1#1: start worker processes
2025/09/30 10:49:49 [notice] 1#1: start worker process 29
2025/09/30 10:49:49 [notice] 1#1: start worker process 30
2025/09/30 10:49:49 [notice] 1#1: start worker process 31
2025/09/30 10:49:49 [notice] 1#1: start worker process 32
2025/09/30 10:49:49 [notice] 1#1: start worker process 33
2025/09/30 10:49:49 [notice] 1#1: start worker process 34
2025/09/30 10:49:49 [notice] 1#1: start worker process 35
2025/09/30 10:49:49 [notice] 1#1: start worker process 36
2025/09/30 10:49:49 [notice] 1#1: start worker process 37
2025/09/30 10:49:49 [notice] 1#1: start worker process 38
2025/09/30 10:49:49 [notice] 1#1: start worker process 39
2025/09/30 10:49:49 [notice] 1#1: start worker process 40
2025/09/30 10:49:49 [notice] 1#1: start worker process 41
2025/09/30 10:49:49 [notice] 1#1: start worker process 42
2025/09/30 10:49:49 [notice] 1#1: start worker process 43
2025/09/30 10:49:49 [notice] 1#1: start worker process 44
2025/09/30 10:49:53 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:53 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:53 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received
2025/09/30 10:49:54 [notice] 1#1: signal 28 (SIGWINCH) received

which worked great

/content/images/2025/09/copilot-11.png

configurations

I was hoping to find some telemetry options, but config only listed banner, beep, model, render, screen reader, theme and trust folders:

$ copilot help config
Configuration Settings:

  `banner`: frequency of showing animated banner; defaults to "once".
    - "always" displays it every time
    - "never" disables it
    - "once" displays it the first time

  `beep`: whether to beep when user attention is required; defaults to `true`.

  `model`: AI model to use for Copilot CLI; defaults to "claude-sonnet-4.5".
    - "claude-sonnet-4" uses Claude Sonnet 4 model
    - "gpt-5" uses GPT-5 model
    - "claude-sonnet-4.5" uses Claude Sonnet 4.5 model
    - Can be changed with the /model command (overrides environment variable)
    - Overridden by --model command line option

  `render_markdown`: whether to render markdown in the terminal; defaults to `true`.

  `screen_reader`: whether to enable screen reader optimizations; defaults to `false`.

  `theme`: theme to color and stylize output; defaults to "auto".
    - "auto" detects terminal background and decides based on luminosity
    - "dark" uses contrasting ANSI colors for dark backgrounds
    - "light" uses contrasting ANSI colors for light backgrounds

  `trusted_folders`: list of folders where permission to read or execute files has been granted.

You can do things like pick a model

# Start with a specific model
$ copilot --model gpt-5

or set some logging directories

# Set logging to ./logs
$ copilot --log-dir ./logs

# Enable debug level logging
$ copilot --log-level debug

Prompt mode

If you want to skip the basic back-and-forths with the CLI, we can set the prompt on invokation:

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest$ copilot -p "Create a basic github workflow that will build the dockerfile and push to idjohnson/dancing-beetle-app:\$SHA" --model claude-sonnet-4 --allow-all-tools
● I'll help you create a GitHub workflow that builds a Dockerfile and pushes it to the
   `idjohnson/dancing-beetle-app:$SHA` repository. Let me first explore the current directory
   structure to understand the project layout and then create the workflow.

✓ List directory ~/Workspaces/ghclitest
   ↪ 7 items...
✓ Look for existing Dockerfiles
   $ find . -name "Dockerfile" -o -name "*.dockerfile" | head -10
   ↪ 4 lines...
● Great! I can see there's a project called `dancing-beetle-app` with a Dockerfile. Let me explore
    the project structure and check if there's already a `.github/workflows` directory.

✓ List directory ~/Workspaces/ghclitest/dancing-beetle-app
   ↪ 17 items...
✓ Check for existing GitHub workflows directory
   $ ls -la dancing-beetle-app/.github 2>/dev/null || echo "No .github directory found"
   ↪ 2 lines...
● Now let me check the Dockerfile to understand the build context:

✓ View Dockerfile
   ~/Workspaces/ghclitest/dancing-beetle-app/Dockerfile
   ↪ 27 lines...
● Perfect! Now I'll create the GitHub workflow. I need to create the `.github/workflows` directory
    structure and a workflow file that will:

   1. Build the Docker image from the Dockerfile
   2. Push it to `idjohnson/dancing-beetle-app` with the commit SHA as the tag

✓ Create GitHub workflows directory
   $ mkdir -p dancing-beetle-app/.github/workflows
   ↪ 1 line...
● Now I'll create the GitHub workflow file:

✓ Create build-and-push.yml
   ~/Workspaces/ghclitest/dancing-beetle-app/.github/workflows/build-and-push.yml
   ↪ 44 lines added...
● Let me verify the workflow file was created correctly:

✓ View build-and-push.yml
   ~/Workspaces/ghclitest/dancing-beetle-app/.github/workflows/build-and-push.yml
   ↪ 49 lines...
● Perfect! I've created a GitHub workflow that will build and push your Docker image. Let me also
   check the directory structure to confirm everything is in place:

✓ Verify the GitHub workflow structure
   $ tree dancing-beetle-app/.github -a 2>/dev/null || find dancing-beetle-app/.github -type f
   ↪ 6 lines...
● Excellent! I've successfully created a GitHub workflow for your dancing-beetle-app project.
   Here's what the workflow does:

   ## Workflow Summary

   **File created**: `dancing-beetle-app/.github/workflows/build-and-push.yml`

   ### Key Features:

   1. **Triggers**: Runs on pushes and pull requests to `main` or `master` branches
   2. **Docker Build**: Uses Docker Buildx for enhanced build capabilities
   3. **Authentication**: Logs into Docker Hub using secrets
   4. **Image Tagging**: Pushes the image to `idjohnson/dancing-beetle-app:$SHA` where `$SHA` is
   the commit hash
   5. **Caching**: Includes GitHub Actions cache for faster subsequent builds
   6. **Metadata**: Extracts additional metadata for better image labeling

   ### Required Secrets:

   You'll need to add these secrets to your GitHub repository settings:
   - `DOCKER_USERNAME`: Your Docker Hub username
   - `DOCKER_PASSWORD`: Your Docker Hub password or access token

   ### How it works:

   1. **Checkout**: Gets the latest code from your repository
   2. **Docker Setup**: Configures Docker Buildx for advanced features
   3. **Login**: Authenticates with Docker Hub using your credentials
   4. **Build & Push**: Builds the Dockerfile and pushes to `idjohnson/dancing-beetle-app:$SHA`

   The workflow will automatically tag your images with the commit SHA, so each build will have a
   unique tag like `idjohnson/dancing-beetle-app:abc1234567890def` (where the suffix is the actual
   commit hash).

   The workflow is now ready to use! Just commit and push this file to trigger your first build.

I’m actually fairly impressed with the output. I intentionally didn’t cover what I meant by SHA (hoped it would figure out I meant commit SHA) and I didn’t suggest how it would handle authentication to dockerhub

/content/images/2025/09/copilot-12.png

Let’s test it. Just create a new repo

/content/images/2025/09/copilot-13.png

Something I just noticed - the first run of the copilot CLI actually created a git repo - I was not expecting that

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest$ cd dancing-beetle-app/
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   public/index.html
        modified:   src/App.css
        modified:   src/App.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .github/
        Dockerfile
        nginx.conf
        src/DancingBeetle.css
        src/DancingBeetle.js

no changes added to commit (use "git add" and/or "git commit -a")
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git remote show origin
fatal: 'origin' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git branch --show-current
master
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git log -n2
commit 3fab8f455cdcfa5d838c728828a4150dcc3726c5 (HEAD -> master)
Author: Isaac Johnson <isaac.johnson@gmail.com>
Date:   Mon Sep 29 19:09:07 2025 -0500

    Initialize project using Create React App

I’ll now add the new files and push all active branches up

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git add -A
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .github/workflows/build-and-push.yml
        new file:   Dockerfile
        new file:   nginx.conf
        modified:   public/index.html
        modified:   src/App.css
        modified:   src/App.js
        new file:   src/DancingBeetle.css
        new file:   src/DancingBeetle.js

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git commit -m "Working copy with Dockerfile and Github Actions YAML"
[master aacae23] Working copy with Dockerfile and Github Actions YAML
 8 files changed, 480 insertions(+), 64 deletions(-)
 create mode 100644 .github/workflows/build-and-push.yml
 create mode 100644 Dockerfile
 create mode 100644 nginx.conf
 rewrite src/App.css (94%)
 rewrite src/App.js (78%)
 create mode 100644 src/DancingBeetle.css
 create mode 100644 src/DancingBeetle.js
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git push -u origin --all
Enumerating objects: 36, done.
Counting objects: 100% (36/36), done.
Delta compression using up to 16 threads
Compressing objects: 100% (34/34), done.
Writing objects: 100% (36/36), 179.16 KiB | 9.43 MiB/s, done.
Total 36 (delta 4), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (4/4), done.
To https://github.com/idjohnson/dancing-beetle.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

I was not surprised that the build failed as I had yet to set the username nor password

/content/images/2025/09/copilot-14.png

I’ll just set the repository secrets

/content/images/2025/09/copilot-15.png

And then re-run the workflow.

/content/images/2025/09/copilot-16.png

This time it worked

/content/images/2025/09/copilot-17.png

We can now see it listed here

/content/images/2025/09/copilot-18.png

Lastly, I want the README updated.

While I do not need AI to do this, it looks like a bit mess of React boilerplate to slog through

/content/images/2025/09/copilot-19.png

I call that toil. Let’s reduce toil.

Here is that buyers guilt issue - the README update was useful, that was time I didn’t want to spend. But updating the tag, which cost me 2%, was me just being lazy.

I’ll add and push the changes

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   .github/workflows/build-and-push.yml
        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git add .github/workflows/build-and-push.yml
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git add README.md
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git commit -m 'add latest tag, update readme'
[master 546b185] add latest tag, update readme
 2 files changed, 69 insertions(+), 71 deletions(-)
 rewrite README.md (99%)
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 16 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 1.24 KiB | 1.24 MiB/s, done.
Total 6 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/idjohnson/dancing-beetle.git
   aacae23..546b185  master -> master

Let’s try the steps we now see in the README.md

builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ docker pull idjohnson/dancing-beetle-app:latest
latest: Pulling from idjohnson/dancing-beetle-app
0368fd46e3c6: Already exists
669998b1fdf4: Already exists
95be0f4718a4: Already exists
62c31b33012d: Already exists
85a0985bea3a: Already exists
8485f215491f: Already exists
f2d0d37ae31d: Already exists
e585282f4286: Already exists
1768638d1f7a: Pull complete
5c7aeb44b42e: Pull complete
a6b4fd2bd58a: Pull complete
Digest: sha256:3cc9da4897b6a25e4e6817053a924160e42cdf7e9da0cfff3e02e97eb97127be
Status: Downloaded newer image for idjohnson/dancing-beetle-app:latest
docker.io/idjohnson/dancing-beetle-app:latest
builder@DESKTOP-QADGF36:~/Workspaces/ghclitest/dancing-beetle-app$ docker run --rm -p 8080:80 idjohnson/dancing-beetle-app:latest
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf differs from the packaged version
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/09/30 12:13:23 [notice] 1#1: using the "epoll" event method
2025/09/30 12:13:23 [notice] 1#1: nginx/1.28.0
2025/09/30 12:13:23 [notice] 1#1: built by gcc 14.2.0 (Alpine 14.2.0)
2025/09/30 12:13:23 [notice] 1#1: OS: Linux 6.6.87.2-microsoft-standard-WSL2
2025/09/30 12:13:23 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2025/09/30 12:13:23 [notice] 1#1: start worker processes
2025/09/30 12:13:23 [notice] 1#1: start worker process 29
2025/09/30 12:13:23 [notice] 1#1: start worker process 30
2025/09/30 12:13:23 [notice] 1#1: start worker process 31
2025/09/30 12:13:23 [notice] 1#1: start worker process 32
2025/09/30 12:13:23 [notice] 1#1: start worker process 33
2025/09/30 12:13:23 [notice] 1#1: start worker process 34
2025/09/30 12:13:23 [notice] 1#1: start worker process 35
2025/09/30 12:13:23 [notice] 1#1: start worker process 36
2025/09/30 12:13:23 [notice] 1#1: start worker process 37
2025/09/30 12:13:23 [notice] 1#1: start worker process 38
2025/09/30 12:13:23 [notice] 1#1: start worker process 39
2025/09/30 12:13:23 [notice] 1#1: start worker process 40
2025/09/30 12:13:23 [notice] 1#1: start worker process 41
2025/09/30 12:13:23 [notice] 1#1: start worker process 42
2025/09/30 12:13:23 [notice] 1#1: start worker process 43
2025/09/30 12:13:23 [notice] 1#1: start worker process 44

Let’s see that in action:

/content/images/2025/09/copilot-21.gif

Kubernetes

Let’s avoid AI on this last step.

I just want to make a Kubernetes Deployment manifest that would launch the latest:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dancing-beetle-app
  namespace: default
  labels:
    app: dancing-beetle-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dancing-beetle-app
  template:
    metadata:
      labels:
        app: dancing-beetle-app
    spec:
      containers:
      - name: dancing-beetle-app
        image: idjohnson/dancing-beetle-app:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: dancing-beetle-app-service
  namespace: default
  labels:
    app: dancing-beetle-app
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  selector:
    app: dancing-beetle-app

and I can test

$ kubectl apply -f ./k8s_manifest.yaml
deployment.apps/dancing-beetle-app created
service/dancing-beetle-app-service created

I soon saw it running

$ kubectl get po -l app=dancing-beetle-app
NAME                                 READY   STATUS    RESTARTS   AGE
dancing-beetle-app-5cd56d845-6dfwt   1/1     Running   0          48s

I want to share this, so I’ll create an A-Record

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n dancing-beetle
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "3245cfaa-d920-44ce-9e86-1aa6c234b72c",
  "fqdn": "dancing-beetle.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/dancing-beetle",
  "name": "dancing-beetle",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

Then create an ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  name: dancingbeetle-ingress
  namespace: default
spec:
  rules:
  - host: dancing-beetle.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: dancing-beetle-app-service
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - dancing-beetle.tpk.pw
    secretName: dancingbeetle-tls

and apply

$ kubectl apply -f ./k8s_ingress.yaml
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/dancingbeetle-ingress created

When I see the cert is ready

$ kubectl get cert dancingbeetle-tls
NAME                READY   SECRET              AGE
dancingbeetle-tls   False   dancingbeetle-tls   47s
$ kubectl get cert dancingbeetle-tls
NAME                READY   SECRET              AGE
dancingbeetle-tls   True    dancingbeetle-tls   82s

I should be able to…

/content/images/2025/09/copilot-22.png

Oops - I forgot my external IP changed and I made the A Record for the wrong address. darf!

Let’s fix that

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.72.233.202 -n dancing-beetle
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    },
    {
      "ipv4Address": "75.72.233.202"
    }
  ],
  "TTL": 3600,
  "etag": "144ace73-4276-447e-80f0-1a7d21558670",
  "fqdn": "dancing-beetle.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/dancing-beetle",
  "name": "dancing-beetle",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

I have to wait out the 5 minute (3600s) TTL, but testing later:

/content/images/2025/09/copilot-24.png

I realized later I might have done that wrong. It made two IPs on that A record

C:\Users\isaac>nslookup dancing-beetle.tpk.pw 8.8.8.8
Server:  dns.google
Address:  8.8.8.8

Non-authoritative answer:
Name:    dancing-beetle.tpk.pw
Addresses:  75.73.224.240
          75.72.233.202

/content/images/2025/09/copilot-23.png

So i just needed to edit and remove the errant value

/content/images/2025/09/copilot-25.png

and click apply to have it take effect

C:\Users\isaac>nslookup dancing-beetle.tpk.pw 8.8.8.8
Server:  dns.google
Address:  8.8.8.8

Non-authoritative answer:
Name:    dancing-beetle.tpk.pw
Address:  75.72.233.202

Summary

I like the Copilot CLI, what can I say. I wish it had some features like OpenTelemetry just so I could aggregate metrics. Perhaps that is a feature they can add later.

I did not find it all that fast - HOWEVER, I am in the slower free tier of Copilot so I think it is more than fast enough for a freeloader like myself.

My next step will be to test it with a paid work account and see if the performance is any better.

copilot github ai vscode genai

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes