GitHub AI Engineering Academy · Part 4 of 16
GitHub Copilot with VS Code: Complete Guide for DevOps Engineers
Academy curriculum (16 lessons)
Visual Studio Code is where a lot of DevOps work actually happens — editing Terraform, tweaking Kubernetes manifests, writing Bash and Python, and wiring up GitHub Actions, all in one window next to an integrated terminal. GitHub Copilot lives inside that window, turning a comment or a chat prompt into a working first draft. But everything it generates is a draft to review, validate, scan, and test — never a decision to ship unread.
This is Part 4 of the GitHub AI Engineering Academy. Part 2, GitHub Copilot for DevOps Engineers, introduced Copilot across the DevOps toolchain; Part 3, GitHub Copilot CLI, took it to the command line. This lesson is about using Copilot productively inside the VS Code IDE, framing every generated artifact the same way: Generate → Understand → Validate → Test → Review → Commit.
What You’ll Learn
- How Copilot is set up in VS Code — the built-in, sign-in-with-GitHub path, and why to verify current steps.
- The interaction surfaces — inline completions, Copilot Chat with Ask / Edit / Agent modes, Inline and Quick Chat, slash commands, and workspace context.
- Repository exploration — using
@workspaceand#codebaseto understand an unfamiliar DevOps repo, and why you verify the answers. - Copilot across the stack — Bash, Python, Dockerfiles, Docker Compose, Terraform, Kubernetes, and GitHub Actions, each generated then hardened.
- Explaining and debugging infrastructure — the strongest use for senior engineers, with a real multi-file failure walked end to end.
- Refactoring, documentation, and tests — reviewing large changes in small diffs and keeping AI docs honest.
- The IDE as a whole — extensions, the integrated terminal, and a unified edit-to-pull-request flow.
- Security and trust — what never to paste, the failure modes to expect, and the review pipeline that catches them.
- 25 reusable prompts and a hands-on lab that builds a small AI-assisted DevOps repository.
Before diving in, it helps to picture the repository you are working in. A typical DevOps repo looks like this:
my-service/
application/ app code
infrastructure/
terraform/ IaC
ansible/ config mgmt
kubernetes/ manifests
docker/ Dockerfiles
scripts/ Bash / Python
.github/workflows/ CI/CD pipelines
docs/ runbooks, READMEs
Copilot sees this tree as context. The workflow this lesson teaches wraps that context in review at every step:
Repository
|
VS Code
|
Copilot (Generate / Explain / Refactor)
|
Engineer Review <-- required
|
Validate
|
Git Commit
|
Pull Request
The two review-shaped steps — the engineer reading the output, and the pull request — are what make the speed safe.
Installing VS Code and GitHub Copilot
On current versions of VS Code, Copilot is built in. You do not hunt for a plugin first: you install VS Code from code.visualstudio.com, open it, and sign in with your GitHub account to enable Copilot. Once signed in, inline completions and Copilot Chat become available in the editor.
Historically, Copilot shipped as two separate Marketplace extensions — “GitHub Copilot” (completions) and “GitHub Copilot Chat” (the chat panel). You will still find guides and screenshots referencing those. The current, documented path is the built-in sign-in above; if your VS Code build still surfaces the extensions, installing and signing into them achieves the same thing.
A free tier with monthly usage limits exists, and paid plans lift those limits. Do not assume specific numbers — plans and quotas change.
❗ Important — VS Code Copilot features, menu names, and setup steps evolve quickly. Verify the exact current names and the sign-in flow at code.visualstudio.com/docs/copilot before relying on any specific menu path in this lesson. Where a feature name below might have moved, that is called out.
Confirm you are set up by opening the Copilot Chat panel and asking a trivial question, or by typing a comment in a file and watching for a greyed-out inline suggestion. If nothing appears, check that you are signed in and that your account has Copilot access.
Understanding Copilot Inside VS Code
Copilot is not one feature but a set of surfaces. Knowing which to reach for is most of the skill.
- Inline code completions — as you type code or a comment, Copilot proposes the next lines in grey; press Tab to accept. It also offers next-edit suggestions, nudging you toward the following logical change after an edit.
- Copilot Chat — a conversational panel with three modes:
- Ask — question-and-answer and explanation. It reads code and answers; it does not change files. Use it to understand a manifest or a workflow.
- Edit — you describe a change and Copilot proposes multi-file edits as a diff you review and accept or reject hunk by hunk.
- Agent mode — you give it a task; it plans, edits across multiple files, and can run terminal commands and self-correct. Crucially, it is approval-gated: you approve or deny each tool or terminal call before it runs, and you can set a permission/autonomy level. Agent mode is powerful for multi-step work, but you are still the one who approves every command.
- Inline Chat and Quick Chat — Inline Chat opens a small prompt right at your cursor for a focused change in the current file; Quick Chat is a lightweight pop-over for a fast question without leaving what you are doing.
- Slash commands in chat —
/explain(explain the selection),/fix(propose a fix),/tests(generate tests), and/doc(add documentation). They target the current selection or file. - Workspace context — prefix a chat question with
@workspaceor reference#codebaseto have Copilot reason over the whole repository rather than only the open file, and add specific files or selections as context. - Integrated-terminal assistance — Copilot can help interpret terminal output and propose commands, tied into the same approval model in Agent mode.
Beyond these, VS Code supports custom instructions (repo-level guidance Copilot follows), prompt files (reusable saved prompts), and MCP servers (connecting Copilot to external tools and data). These are worth knowing exist; treat them as advanced customization and verify their current behavior in the VS Code docs.
🤖 AI Infrastructure Tip — The mode matters. Use Ask to understand before you change anything, Edit when you want a reviewable diff, and Agent only when you are ready to supervise multi-step edits and command runs. The approval prompt in Agent mode is your safety gate — never click through it on autopilot.
Copilot is powered by a rotating set of frontier models; the exact one behind a response changes over time, so this lesson does not hardcode a model name. Check what your account offers in the current GitHub documentation.
AI-Assisted Repository Exploration
The fastest way to get value from Copilot in an unfamiliar DevOps repo is to ask it to explain the codebase before you touch it. Open the repo in VS Code and use @workspace or #codebase so the answers draw on the actual files. Useful opening questions:
- “@workspace explain the overall architecture of this repository and how the pieces fit together.”
- “@workspace where are the container images built, and which Dockerfiles are involved?”
- “@workspace which GitHub Actions workflows deploy to production, and what triggers them?”
- “@workspace where are the Terraform providers configured, and which backends does it use?”
- “@workspace how are Kubernetes secrets referenced by the manifests here?”
- “@workspace which scripts require root or sudo, and what do they do?”
- “@workspace where are environment variables defined and consumed across the app and infra?”
These questions turn a cold repo into a map in minutes. But the map is only as accurate as what Copilot actually read.
⚠️ Warning — Repo-aware answers can be confidently wrong. Copilot may miss a file, summarize stale code, or infer a deployment path that no longer exists. Treat every
@workspace/#codebaseclaim as a lead to confirm — open the workflow it named, read the provider block it pointed to — before you act on it. This matters most for anything about production, secrets, or destructive scripts.
GitHub Copilot with Bash
Take a concrete task: a script that checks filesystem utilization and exits non-zero if any filesystem is over 90% full — the kind of thing you drop into a health check. In a scripts/check_disk.sh file, describe it as a comment and let inline completion draft it, or ask in chat. A rough first draft often looks like this:
#!/bin/bash
# check if any filesystem is over 90 percent
df | awk '{print $5}' | while read use; do
if [ ${use%\%} -gt 90 ]; then
echo "disk full"
exit 2
fi
done
This runs, but it is fragile: no strict mode, the exit 2 runs in a subshell (the pipe) so it does not exit the script, it parses the header row, and it reports no useful detail. Ask Copilot to “make this production-ready: strict mode, skip the header, report the mounts over threshold, exit 2 if any exceed it, and make the threshold an argument.” A stronger version:
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD="${1:-90}"
check_filesystems() {
local threshold="$1"
local over=0
# -P for portable output; skip the header with NR>1
while read -r use mount; do
if (( use > threshold )); then
printf 'OVER: %s at %s%%\n' "$mount" "$use" >&2
over=1
fi
done < <(df -P | awk 'NR>1 {gsub(/%/,"",$5); print $5, $6}')
return "$over"
}
main() {
if check_filesystems "$THRESHOLD"; then
echo "All filesystems under ${THRESHOLD}%."
exit 0
else
echo "One or more filesystems over ${THRESHOLD}%." >&2
exit 2
fi
}
main "$@"
Read it before trusting it:
set -euo pipefail— exit on error, treat unset variables as errors, and fail a pipeline if any stage fails. Strict mode is the single most valuable habit in shell.- Process substitution
< <(...)— feedingdfoutput into the loop via a redirect, not a pipe, so the loop runs in the current shell andoversurvives. This fixes the subshell-exit bug in the draft. - Quoting — every variable is quoted (
"$mount","$THRESHOLD"), so paths with spaces do not break parsing. - Argument parsing —
THRESHOLD="${1:-90}"takes an optional first argument, defaulting to 90. - Exit codes —
0when healthy,2when a threshold is breached, so a monitoring system can act on it.
Then validate: run shellcheck scripts/check_disk.sh to catch quoting and portability issues, and test it against known conditions — a normal machine (expect exit 0) and, safely, a fake input to force the over-threshold branch.
✅ Best Practice — Ask for the rough version to see the shape, then explicitly ask Copilot to harden it — strict mode, quoting, functions, argument parsing, real exit codes — and finish with
shellcheck. Generate, understand, validate, test — never generate and trust.
GitHub Copilot with Python
Python is where a lot of DevOps glue lives: call an API, parse the result, decide something. Take scripts/check_services.py: call a REST health endpoint, parse the JSON, identify failed services, print a readable summary, and exit non-zero if anything is down. Ask Copilot for exactly that, then refine with “add type hints, argparse, logging, docstrings, and exception handling.” A reviewable result:
#!/usr/bin/env python3
"""Check service health from a JSON status API and report failures."""
import argparse
import logging
import sys
import urllib.error
import urllib.request
import json
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
log = logging.getLogger("check_services")
def fetch_status(url: str, timeout: int = 10) -> dict:
"""Fetch and parse the JSON status document from the given URL."""
request = urllib.request.Request(url, headers={"Accept": "application/json"})
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as exc:
log.error("HTTP error %s: %s", exc.code, exc.reason)
except urllib.error.URLError as exc:
log.error("Network error: %s", exc.reason)
except json.JSONDecodeError as exc:
log.error("Invalid JSON in response: %s", exc)
return {}
def failed_services(status: dict) -> list[str]:
"""Return names of services whose status is not 'ok'."""
services = status.get("services", {})
return [name for name, state in services.items() if state != "ok"]
def main() -> int:
parser = argparse.ArgumentParser(description="Check service health.")
parser.add_argument("url", help="URL of the JSON status endpoint")
args = parser.parse_args()
status = fetch_status(args.url)
if not status:
log.error("No status data; treating as failure.")
return 1
failures = failed_services(status)
if failures:
log.error("Failed services: %s", ", ".join(failures))
return 1
log.info("All services healthy.")
return 0
if __name__ == "__main__":
sys.exit(main())
Review points before this goes anywhere near a cron job or a CI step:
- Type hints and docstrings make intent explicit and give the next reader (and Copilot) accurate context.
- Exception handling distinguishes HTTP, network, and JSON errors and logs each, returning an empty dict rather than crashing.
- Logging, not
print— output goes through the logging module at appropriate levels, sostderrcarries the failures. argparsemakes the endpoint a real argument with a--helpfor free.- Exit codes — non-zero on any failure, so a caller can react.
Now ask Copilot /tests for failed_services and fetch_status, and read the generated tests critically — mocking the HTTP call, asserting on the failure list. Generated tests are a starting point, not proof of correctness; verify they actually assert the behavior you care about. The Bash and Python automation guides go deeper on scripting patterns.
GitHub Copilot with Dockerfiles
Ask Copilot for a Dockerfile for a Python service and you will get a working but naive one — single stage, root user, no health check. Rather than repeat Part 2’s full before/after, focus on the workflow: generate the basic file, then ask Copilot to “improve this for production.” A hardened result:
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS runtime
ENV PATH="/opt/venv/bin:$PATH" \
PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd --create-home --uid 10001 appuser
COPY --from=build /opt/venv /opt/venv
COPY . .
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD ["python", "app.py"]
Have Copilot explain each change and confirm it in the diff:
- Multi-stage build keeps the compiler and build cruft out of the final image.
- Non-root
appusermeans a container escape does not begin as root — one of the highest-value hardening steps. - Dependency caching — copying
requirements.txtand installing before the source means the dependency layer is reused when only code changes. - Minimal base and deterministic config —
slimbase, pinned Python version,--no-cache-dir, and explicitENV. - Health check so orchestrators know the app is actually serving.
⚠️ Warning — A Copilot Dockerfile is not automatically secure. It may pull an unpinned or outdated base image, skip the non-root user, or add a health check that hits the wrong path. Read every line, pin what matters, build it, and scan the image before pushing. The Docker guides and hands-on Docker Academy go further.
GitHub Copilot with Docker Compose
For local development, describe the stack — “FastAPI app, PostgreSQL, and Redis, with health checks, named volumes, and a private network” — and refine what Copilot drafts:
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/appdb
REDIS_URL: redis://cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
restart: unless-stopped
networks: [backend]
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
networks: [backend]
cache:
image: redis:7
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks: [backend]
volumes:
pgdata:
redisdata:
networks:
backend:
What to check:
- Health-gated dependencies —
depends_onwithcondition: service_healthywaits for the database and cache to actually accept connections, not merely to start. That is what the health checks enable. - Environment and secrets —
${DB_PASSWORD}comes from a local.envyou add to.gitignoreand never commit. Replace any literal password Copilot inlines. For production, use a real secrets mechanism rather than.env. - Volumes — named volumes persist Postgres and Redis data across restarts.
- Networks — a private
backendnetwork keeps services talking to each other without exposing more than theapiport. - Restart policies —
unless-stoppedon the app is reasonable for local resilience.
This is a development stack. Do not carry exposed ports, default users, and unauthenticated Redis into production unchanged.
GitHub Copilot with Terraform
Terraform is where Copilot is both most helpful and most dangerous — helpful because HCL is verbose and mechanical, dangerous because a plausible change can destroy stateful resources or widen access. A typical infrastructure/terraform/ project splits into main.tf, variables.tf, outputs.tf, providers.tf, and versions.tf. Copilot is good at drafting each and at wiring them together.
Ask it for the version and provider scaffolding first:
# versions.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Then a typed variable, a resource that uses it, validation, and an output:
# variables.tf
variable "bucket_name" {
description = "Name of the artifacts bucket"
type = string
validation {
condition = can(regex("^[a-z0-9.-]{3,63}$", var.bucket_name))
error_message = "Bucket name must be 3-63 chars, lowercase."
}
}
# main.tf
resource "aws_s3_bucket" "artifacts" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_versioning" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
versioning_configuration {
status = "Enabled"
}
}
# outputs.tf
output "bucket_arn" {
description = "ARN of the artifacts bucket"
value = aws_s3_bucket.artifacts.arn
}
Copilot handles the mechanical work well here: a typed variable with a validation block, resources that reference each other, and an output. Ask it in chat to explain the validation regex or to extract a repeated pattern into a module — both are strong uses.
What it cannot know is your account: which bucket names exist, what encryption and tagging your policy requires, or whether an argument change forces resource replacement. So AI-generated IaC goes through a strict pipeline before it applies:
Copilot Suggestion
|
terraform fmt
|
terraform validate
|
tflint
|
security scan
|
terraform plan
|
Engineer Review <-- read every change
|
Apply
⚠️ Warning — Never
terraform applyAI-generated HCL without reading a fullterraform plan. A confident suggestion can propose destroying and recreating a database, widening an IAM policy, or removing a protection. Runfmt,validate,tflint, and a security scanner, then read the plan line by line and apply in a non-production workspace first. IaC is uniquely sensitive because mistakes cost data, uptime, and money. See the Terraform guides and OpenTofu guides.
GitHub Copilot with Kubernetes
Build a Deployment and Service incrementally. Start by asking for a minimal Deployment plus a Service, then improve it. Ask Copilot to “add readiness, liveness, and startup probes; resource requests and limits; a hardened securityContext; and reference config from a ConfigMap and a Secret.” A production-shaped result:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-api
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: ai-api
template:
metadata:
labels:
app: ai-api
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: ai-api
image: myorg/ai-api:1.0.0
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: ai-api-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ai-api-secrets
key: db-password
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
startupProbe:
httpGet:
path: /health
port: 8000
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
name: ai-api
spec:
selector:
app: ai-api
ports:
- port: 80
targetPort: 8000
Each addition is a production requirement: the startup probe protects a slow-booting app from being killed before it is ready; readiness/liveness control traffic and restarts; requests/limits let the scheduler place pods and protect the node; securityContext drops root, blocks privilege escalation, and drops all capabilities; and ConfigMap/Secret refs keep configuration and credentials out of the manifest. The rolling update with maxUnavailable: 0 keeps capacity during deploys.
✅ Best Practice — Never put real credentials in YAML. Reference a Secret (as above), and manage the Secret’s contents outside the manifest. Validate with
kubectl apply --dry-run=server -f deployment.yamlbefore applying. The Kubernetes and Helm guides cover probes, rollout strategy, and resources in depth.
GitHub Copilot with GitHub Actions and YAML
CI/CD workflows are a strong Copilot use, and the stable building blocks are verified. Describe the pipeline — “on push and PR: check out, set up Python, install, lint, test, build a container, and scan it, with least-privilege permissions” — and refine:
name: ci
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install --no-cache-dir -r requirements.txt
- name: Lint
run: ruff check .
- name: Run tests
run: pytest -q
- name: Build container
run: docker build -t myorg/ai-api:${{ github.sha }} .
- name: Scan image
run: |
# Run your scanner (e.g. Trivy) and fail on serious findings
echo "scan the built image before publishing"
The parts to verify in any Copilot-generated workflow:
- Pinned, real action versions —
actions/checkout@v4andactions/setup-python@v5are current. Reject any unversioned or inventeduses:. - Least-privilege
permissions:— the top-levelcontents: readstarts the workflow with minimal access; add more only where a step needs it. Copilot often omits this — add it. - Secrets, never literals — reference
${{ secrets.NAME }}; never inline a token. - A scan that actually fails — a placeholder that always passes is worse than none.
Ask Copilot to explain a step, add dependency caching, add a conditional publish (if: github.ref == 'refs/heads/main'), or reduce permissions. It is also good at troubleshooting a failed run — paste the log and ask why.
YAML deserves its own attention because it is error-prone: significant whitespace, tabs-versus-spaces, quoting surprises, and silent duplicate keys. Copilot is genuinely useful at spotting indentation errors, invalid keys, wrong nesting, duplicated keys, and schema mistakes across Actions, Kubernetes, Compose, and Ansible. Ask “@workspace check this workflow for YAML and schema errors.” But do not stop at Copilot — run real schema validation (kubectl --dry-run=server, docker compose config, ansible-lint, a workflow linter) as the authority.
🤖 AI Infrastructure Tip — Copilot sometimes suggests third-party Actions by name. Before adding one, confirm it exists, is maintained, and is pinned to a version or commit SHA. An unverified third-party Action runs with access to your repo and secrets. See the security hardening guides.
Explaining and Debugging Infrastructure
For senior engineers, Copilot’s single strongest use in VS Code is not generation — it is explanation. Select a file or use @workspace and ask:
- “Explain what this Terraform module does and what it will create or change.”
- “Trace how external traffic reaches this Kubernetes Service, step by step.”
- “Explain this GitHub Actions workflow step by step, including what each job depends on.”
- “Explain how the services in this Compose file reach each other over the network.”
- “Explain what this Ansible role changes on the target host.”
- “Point out any destructive commands in this Bash script and what they would delete.”
Because these are read-only questions, they are low-risk and high-value — a fast way to build a mental model of unfamiliar infrastructure. You still confirm the explanation against the code.
When something is broken, use a disciplined loop rather than pasting an error and accepting the first answer:
Error
|
Code
|
Logs
|
Copilot Explanation (hypothesis)
|
Verify
|
Fix
|
Tests
Work a concrete case: a Python container keeps failing its Kubernetes readiness probe, so the pod never becomes Ready. The failure has no single cause — it emerges from several files agreeing or disagreeing:
- The app — which port does it actually bind, and does it serve the probe path? Ask Copilot
/explainon the app’s startup code. - The Dockerfile — does
EXPOSEand the runtime match that port? Is the app even listening on0.0.0.0rather than127.0.0.1? - The Deployment — does
containerPortmatch the app port, and does thereadinessProbe.httpGet.path/portpoint at a route the app really serves? - The Service — does
targetPortmatch the container port? - The logs —
kubectl logsandkubectl describe podfor the probe failure events.
Paste the probe error and the relevant snippets and ask “@workspace why is this readiness probe failing?” Copilot will usually propose a hypothesis — often a port or path mismatch between the Deployment and the app. Verify it by checking the actual bind address and route, fix the one file that is wrong, and add or adjust a test so the mismatch cannot recur silently. The lesson: one symptom, several contributing files, and Copilot is best used to narrow the search, not to pronounce the verdict.
🔍 Troubleshooting — When multiple files contribute to one failure, give Copilot all of them as context (
@workspaceor by adding each file), not just the error. An explanation based on the error string alone is a guess; an explanation that has seen the Deployment, Service, Dockerfile, and app is a much better lead — still to be verified.
Refactoring, Docs, and Tests
Refactoring. Copilot handles mechanical restructuring well. Useful prompts: “refactor this Terraform into a reusable module with variables and outputs,” “convert these raw Kubernetes manifests into a Helm chart with a values file,” “rewrite this Bash script in Python with proper error handling,” “add error handling and input validation to this Ansible role,” and “reduce the duplication across these GitHub Actions jobs.” The catch is review scale: a large refactor produces a large diff.
✅ Best Practice — Review large refactors in small diffs. Use Edit mode so changes arrive as reviewable hunks, accept them in pieces, and run tests between steps. A 400-line “improved” file accepted in one click is unreviewable; the same change in ten hunks is not.
Documentation. Ask Copilot to draft a README, document a Terraform module’s variables and outputs, add a usage block to a Bash script, write Python docstrings, explain a workflow, or produce a first-pass runbook. This is a real time-saver — with one hazard.
⚠️ Warning — AI-generated docs drift from the implementation. Copilot documents what the code looks like it does, which is not always what it does, and docs are never re-checked when code changes. Treat generated docs as a draft you verify against the real behavior, and update them when the code moves.
Tests. Frame test generation as Generate → Validate → Correct, never Generate → Trust:
- Python —
/testsfor unit tests and API tests; check that assertions actually exercise the behavior, not just that code runs. - Bash —
shellcheckfor static issues andbatsfor behavior; ask Copilot to draftbatscases and verify they test the exit codes you care about. - Terraform —
terraform validateandtflint, plus policy checks; Copilot can draft policy rules you then confirm. - Kubernetes — manifest and schema validation (
--dry-run=server); Copilot can generate manifests but the API server is the authority. - Docker — build the image and exercise the health check; a passing
docker buildis necessary, not sufficient.
VS Code, Extensions, and the Integrated Terminal
VS Code’s value for DevOps is that the whole toolchain sits in one window. Beyond Copilot itself, common extension categories (verify exact names and publishers before installing) include:
- GitHub Copilot — completions and chat (built in on current builds).
- GitHub Pull Requests and Issues — review and manage PRs and issues without leaving the editor.
- Docker / container tooling — build, run, and inspect images and containers.
- Kubernetes tooling — browse clusters, view resources, and edit manifests.
- Terraform / HCL tooling — syntax, formatting, and validation for HCL.
- YAML tooling — schema validation and linting for the many YAML files DevOps touches.
- Python tooling — language support, linting, and debugging.
- Remote Development — work inside containers, WSL, or over SSH, so the editor runs against the real environment.
The integrated terminal ties it together. A tight, reviewable loop for infrastructure looks like this:
Edit Terraform (in VS Code)
|
Ask Copilot (explain / refine)
|
Integrated Terminal
|
terraform validate
|
terraform plan
|
Review Diff
|
Commit
|
Pull Request
Everything — editing, asking Copilot, running validate and plan, reading the diff, committing — happens in one window, which keeps the review step close to the change instead of a context-switch away.
Security, Privacy, and Trust
Copilot reads what you show it, so what you show it matters. Keep the following out of prompts, chat, and any file you paste:
- Secrets, API keys, and tokens
.envfile contents- Production logs (which often contain tokens, connection strings, hostnames, IPs, and personal data)
- Customer or user data
- Credentials of any kind
- Private configuration and internal infrastructure details
- Anything your organization’s policy restricts
Understand your organization’s policies on AI-assisted development before leaning on Copilot for work code — many teams have explicit rules about what may be shared with AI tools.
❗ Important — Do not make or repeat unsupported claims about data retention or model training either way. Whether specific inputs are retained or used is governed by official GitHub documentation and your admin settings — verify there rather than assuming. The reliable safe habit is simple: keep secrets and sensitive data out of prompts, and redact logs before sharing.
Never Blindly Accept Copilot Suggestions
AI-generated code fails in recognizable ways. Knowing the failure modes is how you catch them in review:
- Outdated syntax — deprecated API versions, fields, or flags that no longer exist.
- Hallucinated options — plausible-looking flags, arguments, or resources that are not real.
- Insecure defaults — no TLS, permissive settings, secrets in plaintext.
- Excessive IAM — wildcard actions or
*resources granting far too much. - Privileged containers —
privileged: true, running as root, host mounts. - Insecure images — unpinned or outdated base images with known CVEs.
- Missing resource limits — pods with no requests/limits that can starve a node.
- Destructive Terraform — changes that replace or delete stateful resources.
- Poor Bash quoting — unquoted variables that break on spaces or globs, or worse.
- Hardcoded secrets — tokens and passwords written into code or manifests.
- Vulnerable dependencies — real packages pulled in with known vulnerabilities.
The defense is the same pipeline you would apply to any pull request, whoever — or whatever — wrote it:
Copilot Suggestion
|
Engineer Review
|
Lint
|
Validate
|
Security Scan
|
Test
|
Pull Request
Copilot is a fast, well-read colleague who has never been on call for your systems. Useful — and never the final authority.
25 GitHub Copilot Prompts for VS Code DevOps Workflows
Reusable starting prompts. Each produces a draft to understand, validate, test, and review before you ship.
Exploration and explanation
- “@workspace explain the architecture of this repository and how the components connect.”
- “@workspace which workflows deploy to production and what triggers them?”
- “@workspace where are Terraform providers and backends configured?”
- “@workspace how are Kubernetes secrets referenced across these manifests?”
- “/explain this Terraform module and what it will create or change.”
Bash and Python
- “Write a Bash script that exits 2 if any filesystem is over a threshold, with strict mode and argument parsing.”
- “Add
shellcheck-clean quoting and error handling to this script.” - “Write a Python script that calls a JSON status API and exits non-zero on failures, with type hints and logging.”
- “/tests generate unit tests for this function, mocking the HTTP call.”
- “Convert this Bash script to Python with proper exception handling.”
Docker and Compose
- “Improve this Dockerfile for production: multi-stage, non-root, health check, caching.”
- “Add a
.dockerignorethat excludes caches, build artifacts, and secrets.” - “Write a Compose file for a FastAPI app with PostgreSQL and Redis, using health checks and a private network.”
- “/explain why this container exits immediately and how to debug it.”
Terraform
- “Draft variables, outputs, and a validation block for this Terraform module.”
- “Refactor this Terraform into a reusable module.”
- “/explain what this
terraform planoutput proposes to destroy and why.”
Kubernetes
- “Add startup, readiness, and liveness probes plus resource requests and limits to this Deployment.”
- “Add a hardened securityContext that drops all capabilities and disallows privilege escalation.”
- “Convert these manifests into a Helm chart with a values file.”
GitHub Actions and YAML
- “Write a CI workflow that checks out, sets up Python, installs, lints, tests, builds, and scans, with least-privilege permissions.”
- “Add dependency caching and a publish job gated to the main branch.”
- “Check this YAML for indentation, invalid keys, and duplicate keys.”
Review and safety
- “Review this manifest for security issues before I apply it.”
- “What are the risks of running this command in production, and how would I test it safely first?”
Lab: Build an AI-Assisted DevOps Repository with VS Code
Put it together by building a small repository with Copilot as your assistant — the point is the cycle, not the files. Target this structure:
ai-devops-demo/
app/
main.py FastAPI health endpoint
Dockerfile
compose.yaml
k8s/
deployment.yaml
service.yaml
infrastructure/
terraform/
main.tf
variables.tf
outputs.tf
.github/workflows/
ci.yml
tests/
test_main.py
README.md
Work each artifact through the same loop — Generate → Understand → Validate → Test → Review → Commit:
- App — ask Copilot for a minimal FastAPI app with a
/healthroute that returns{"status": "ok"}. Read it, run it locally, and confirm the route responds. - Dockerfile — generate, then ask Copilot to harden it (multi-stage, non-root, health check). Build the image and confirm it runs as
appuser. - compose.yaml — bring up the app with its dependencies; verify health-gated startup with
docker compose configanddocker compose up. - Kubernetes — generate the Deployment and Service, add probes, limits, and a securityContext; validate with
kubectl apply --dry-run=server. - Terraform — draft a small
infrastructure/terraform/project; runterraform fmt,validate,tflint, and read aterraform planbefore any apply. - CI pipeline — generate
ci.ymlwith pinned actions andpermissions: contents: read; confirm eachuses:is real and the scan step fails on findings. - Tests — use
/testsfor the app, then read and correct the assertions; runpytest -q. - README — have Copilot draft it, then verify every command and path against what you actually built.
Commit each piece only after you have understood and validated it. By the end you will have used inline completions, Ask/Edit/Agent modes, slash commands, and @workspace — and, more importantly, practiced the habit that makes all of it safe: generating fast, then reviewing before you trust.
🛠️ DevOps Tip — Add a repo-level custom instructions file so Copilot follows your conventions (non-root containers, pinned action versions, least-privilege permissions) by default across the project. Verify the current custom-instructions setup in the VS Code docs, since the mechanism evolves.
What’s Next
You now have Copilot working across the full DevOps surface inside VS Code — Bash, Python, Docker, Compose, Terraform, Kubernetes, and Actions — plus exploration, debugging, refactoring, and the review pipeline that keeps it all safe. The next lesson, Part 5: GitHub Copilot with Terraform (coming soon), goes deep on AI-assisted infrastructure as code and the guardrails it demands.
If you arrived here out of order, work back through Part 3, GitHub Copilot CLI, and Part 2, GitHub Copilot for DevOps Engineers, and return to the GitHub AI Engineering Academy home for the full path. To go deeper on the tools here, the hands-on Docker Academy, the Kubernetes and Helm guides, the Terraform guides, and the Ubuntu AI Infrastructure series are all good next steps.
Recommended GitHub Books
GitHub Copilot Unleashed
A deeper dive into AI-assisted development with GitHub Copilot — prompting, workflows, and getting more from the tool.
- Copilot
- AI-assisted development
- Productivity
Ultimate Git and GitHub for Modern Software Development
A broad, practical tour of Git and GitHub for modern development workflows — a solid all-rounder for engineers building on GitHub.
- GitHub
- Workflows
- Foundations
Affiliate Disclosure: Some links on this page are affiliate links. If you purchase through one of these links, DevOps AI Toolkit may earn a commission at no additional cost to you. See our affiliate disclosure.
Frequently asked questions
Do I need to install a separate extension to use GitHub Copilot in VS Code?
Not anymore. Copilot is built into current versions of VS Code — you enable it by signing in with your GitHub account. Historically it shipped as the separate 'GitHub Copilot' and 'GitHub Copilot Chat' Marketplace extensions, and you may still see those references online. Because the setup path changes, confirm the current steps at code.visualstudio.com/docs/copilot rather than assuming. A free tier with monthly limits exists; paid plans lift them — verify what your account has.
What is the difference between Ask, Edit, and Agent mode in Copilot Chat?
Ask mode answers questions and explains code without changing files. Edit mode proposes multi-file edits that you review and accept or reject as a diff. Agent mode plans a task, edits across multiple files, and can run terminal commands and self-correct — but it is approval-gated: you approve or deny each tool or terminal call before it runs, and you can set a permission level. Ask when you want understanding, Edit when you want reviewed changes, Agent when you want a multi-step task carried out under supervision.
How do I give Copilot context about my whole repository?
Use @workspace or #codebase in Copilot Chat to let it reason over the repository rather than just the open file, and add specific files or selections as context. This is how you ask questions like 'which workflow deploys production' or 'where are Terraform providers configured.' The answers are only as good as what the tool actually read, so always verify repo-aware claims against the real code before acting on them.
Can Copilot in VS Code run terminal commands for me?
Yes, in Agent mode and through integrated-terminal assistance it can propose and run commands — but every tool or terminal call is approval-gated. Copilot shows you what it wants to run and waits for you to approve or deny it, and you set the autonomy level. Treat that approval prompt as your review step: read the command, understand what it does, and reject anything destructive like terraform destroy, kubectl delete, or rm -rf before it executes.
Is it safe to accept Copilot's Terraform, Kubernetes, or Dockerfile suggestions as-is?
No. AI-generated infrastructure regularly ships insecure defaults: containers running as root, missing resource limits, over-permissive IAM, exposed Services, deprecated API versions, and destructive Terraform changes. Copilot produces a fast first draft, not a production decision. Run it through the same pipeline you would use for any pull request — engineer review, lint and validate, security scan, test environment, and human approval — before it touches production.
What slash commands does Copilot Chat support in VS Code?
The core ones for DevOps work are /explain (explain selected code or a manifest), /fix (propose a fix for a problem in the selection), /tests (generate tests for the selection), and /doc (add documentation). You can combine them with workspace context like @workspace. VS Code Copilot features evolve quickly, so check the current list in the VS Code docs; treat generated tests and docs as drafts you still review and correct.
Will Copilot leak my secrets or send private code somewhere?
Do not paste secrets, API keys, .env contents, production logs, customer data, or private infrastructure details into any prompt, and follow your organization's policy on AI-assisted development. Whether specific data is retained or used is a question for official GitHub documentation and your admin settings, not assumption — do not rely on unsupported claims either way. The safe habit is to keep credentials and sensitive data out of prompts entirely and redact logs before sharing them.
Does Copilot in VS Code replace the need to know Bash, YAML, or Terraform?
No. Copilot speeds up recall and boilerplate, but you still need to read what it produces, spot insecure or incorrect output, and decide whether it is safe to ship. YAML indentation bugs, wrong Terraform arguments, unquoted shell variables, and hallucinated options all slip through if you cannot evaluate the code. Copilot makes a knowledgeable engineer faster; it does not make an unreviewed suggestion trustworthy.
← Back to GitHub AI Engineering Academy