Skip to main content

Command Palette

Search for a command to run...

I Built an AI Agent That Joins Docker + GitHub + Slack in One SQL Query — Here's How

A deep dive into Coral Watchdog: autonomous DevOps incident detection using Coral SQL, gpt-4o-mini, and a custom Docker source spec

Updated
8 min read
I Built an AI Agent That Joins Docker + GitHub + Slack in One SQL Query — Here's How

I Built an AI Agent That Joins Docker + GitHub + Slack in One SQL Query

Built for the Pirates of the Coral-bean Hackathon · May 2026


It's 3 AM. Your phone buzzes. A container is down.

You open five different tabs — Docker logs, GitHub recent PRs, Slack for who's been notified, Sentry for errors, and your team's on-call schedule. By the time you've pieced together what happened and why, 20 minutes have passed. Your users have been staring at a broken screen the whole time.

What if a single SQL query could answer all of that instantly?

That's exactly what I set out to build. This is the story of Coral Watchdog — an autonomous DevOps incident agent I built for the Pirates of the Coral-bean hackathon by WeMakeDevs.


🪸 What is Coral?

Before we get into the build, let me introduce the technology that made this possible.

Coral is an open-source query layer that lets you run SQL across any API, database, or file — with cross-source JOINs. No ETL pipelines. No data engineering. No glue code.

Instead of writing this:

docker_data = docker_api.get_containers()
github_prs = github_api.list_pulls(owner, repo)
slack_msgs = slack_api.get_messages(channel)

# ...200 lines of joining, transforming, correlating...

You write this:

SELECT
  dc.status,
  gp.title AS last_pr,
  gp.user__login AS deployed_by
FROM docker.containers dc
LEFT JOIN github.pulls gp
  ON gp.owner = 'ghosthouse7' AND gp.repo = 'CORAL-WATCHDOG'
WHERE dc.status LIKE '%Exited%'
ORDER BY gp.merged_at DESC
LIMIT 10

One query. Three live data sources. Real-time results.

That's the magic. And it became the core of everything I built.


🏗️ The Architecture

Here's what Coral Watchdog does end-to-end:

Every 60 seconds:
  ↓
Docker Engine API → find unhealthy containers
  ↓
If found → run Coral SQL query
  (joins Docker + GitHub + Slack simultaneously)
  ↓
Send context to gpt-4o-mini
  ↓
Root cause analysis generated
  ↓
Slack alert posted automatically
  ↓
Live dashboard updated at localhost:8080

And it's all written in Go — a single binary, no external dependencies beyond Coral itself.


🐳 The Custom Docker Source Spec

Here's where things got interesting.

Docker is not a built-in Coral source. So I had to teach Coral how to speak to the Docker Engine REST API.

Coral has a YAML-based DSL for defining custom sources. Here's a simplified version of what I built:

name: docker
version: 0.1.0
dsl_version: 3
backend: http

inputs:
  DOCKER_API_BASE:
    kind: variable
    default: http://localhost:2375

base_url: "{{input.DOCKER_API_BASE}}"

tables:
  - name: containers
    description: "All Docker containers including stopped ones"
    request:
      method: GET
      path: /containers/json?all=true
    columns:
      - name: id
        type: Utf8
      - name: image
        type: Utf8
      - name: status
        type: Utf8
      - name: state
        type: Utf8
      - name: created
        type: Int64

With this spec added via coral source add --file ./sources/docker.yaml, I can now run:

SELECT id, image, status, state FROM docker.containers LIMIT 10

And get live container data as SQL rows. The Docker Engine becomes a queryable database. That's the power of Coral's source spec system.


🔗 The Star Query — Three Sources, One JOIN

This is the query that runs every time a container crashes. It's the centrepiece of the entire project:

SELECT
  dc.id               AS container_id,
  dc.status           AS container_status,
  dc.image            AS image,
  gp.title            AS last_merged_pr,
  gp.merged_at        AS deploy_time,
  gp.user__login      AS deployed_by,
  sc.name             AS slack_channel
FROM docker.containers dc
LEFT JOIN github.pulls gp
  ON gp.owner = 'ghosthouse7'
  AND gp.repo = 'CORAL-WATCHDOG'
  AND gp.state = 'closed'
LEFT JOIN slack.channels sc
  ON sc.name = 'social'
WHERE dc.status LIKE '%Exited%'
   OR dc.status LIKE '%unhealthy%'
ORDER BY gp.merged_at DESC
LIMIT 20

In a single query, I instantly know:

  • ✅ Which container crashed and what its current state is

  • ✅ What PR was last merged (what code was deployed)

  • ✅ Who deployed it

  • ✅ Which Slack channel the incident will be reported to

Without Coral, this would be three separate API calls, authentication for each, 100+ lines of joining logic, and likely a microservice to maintain. With Coral, it's 25 lines of SQL.


🤖 The AI Root Cause Analysis

Once Coral returns the cross-source data, I pass everything to gpt-4o-mini via the GitHub Models API (free for students with the GitHub Student Pack — no credit card needed!):

func Summarize(ctx IncidentContext) (string, error) {
    system := `You are a senior DevOps engineer analysing 
    production incidents. Be concise, specific, and focus 
    on actionable remediation.`
    
    user := fmt.Sprintf(`
Container: %s
State: %s
CPU: %.1f%%  Memory: %.1fMB
Last 50 log lines:
%s
Cross-source Coral query result:
%s`,
        ctx.ContainerName,
        ctx.ContainerState,
        ctx.CPUPercent,
        ctx.MemoryMB,
        strings.Join(ctx.Logs, "\n"),
        ctx.CoralQueryResult,
    )
    
    return callGHModels(system, user)
}

The model receives the container state, recent logs, memory/CPU stats, AND the cross-source Coral result — all in one context window. It comes back with a structured root cause analysis in seconds.


💬 The Slack Alert

The analysis gets posted directly to your incident channel:

🚨 Incident Detected — Container `jaeger` is `Exited (255)`

🐳 Container: jaeger
📦 Image: jaegertracing/all-in-one:latest
⚠️ Status: Exited (255)
🕐 Detected: 2026-05-28 03:12:43 UTC

🤖 AI Root Cause Analysis:
Root cause: OOM kill — the container exceeded its 512MB 
memory limit after SAMPLING_RATE was increased from 10% 
to 100% in PR #42. Memory usage spiked to 618MB.

Recommended fix: Increase memory limit to 1GB in your 
docker-compose.yml, or revert the sampling rate change.

Prevention: Add memory alerts before hitting the limit.

Container ID: `a3f8c2d91b47`
Powered by Coral Watchdog 🏴‍☠️

Zero manual intervention. Zero context switching. The on-call engineer gets everything they need in one Slack message.


🖥️ The Dashboard

I also built a live web dashboard served directly from the Go binary:

  • Overview tab — container health, incident count, sources connected

  • Containers tab — full fleet status with CPU/memory bars

  • Incidents tab — AI-analysed incident feed with summaries

  • Query tab — the NL query interface (my favourite feature)

The dashboard lives at http://localhost:8080 and auto-refreshes every 30 seconds.


✨ The Natural Language Query Interface

The feature I'm most proud of: you type a question in plain English, it becomes a Coral SQL query, runs against your live infrastructure, and shows you results.

Type:

"which containers crashed after the last PR merge?"

gpt-4o-mini generates:

SELECT dc.id, dc.status, gp.title AS pr_title
FROM docker.containers dc
LEFT JOIN github.pulls gp
  ON gp.owner = 'ghosthouse7' AND gp.state = 'closed'
WHERE dc.status LIKE '%Exited%'
ORDER BY gp.merged_at DESC
LIMIT 10

Coral runs it. Results appear. No SQL knowledge required.

This works because of a carefully crafted system prompt that knows the exact table schemas for Docker, GitHub, and Slack:

You are a Coral SQL expert. Convert natural language 
to Coral SQL. Available tables:
- docker.containers: id, image, status, state, created
- github.pulls: title, state, merged_at, user__login
- slack.channels: id, name
RULES: Return ONLY the SQL. No backticks. LIMIT 20 always.

🔧 Tech Stack

Layer Technology
Language Go 1.22
Query Layer Coral (custom Docker source spec)
AI gpt-4o-mini via GitHub Models API
Notifications Slack Incoming Webhooks
Dashboard Vanilla HTML/CSS/JS (single file)
Sources Docker Engine API, GitHub API, Slack API

💡 Key Learnings

1. Coral's power is in the JOIN

Any single source is just an API wrapper. The magic happens when you JOIN Docker container state with the GitHub PR that deployed the code. Suddenly you have context that no single monitoring tool can give you.

2. Building a custom source spec is approachable

I had a working Docker source in about 2 hours. The YAML DSL is well-designed. The key mental model: think of any REST API as a database, and each endpoint as a table.

3. Students don't need to pay for AI

GitHub Student Pack gives you access to gpt-4o-mini and many other models through GitHub Models for free. No credit card, no API billing anxiety during a hackathon.

4. Go is perfect for this kind of agent

Single binary, excellent HTTP client, goroutines for the dashboard server running alongside the watcher. The whole agent compiles to one executable.


🚀 Try It Yourself

# Clone
git clone https://github.com/ghosthouse7/CORAL-WATCHDOG
cd CORAL-WATCHDOG

# Download coral.exe from github.com/withcoral/coral/releases
# Place in project folder

# Create .env with your tokens
cp .env.example .env  # then fill in values

# Add Coral sources
.\coral source add github
.\coral source add slack
.\coral source add --file .\sources\docker.yaml

# Run
go run main.go

# Open dashboard
# http://localhost:8080

The full setup guide is in the README.


🏴‍☠️ What's Next

A few things I'd love to add:

  • Azure Monitor integration — join cloud metrics with container state

  • PagerDuty auto-escalation — create incidents automatically

  • Historical incident patterns — "this container crashes every Monday"

  • Slack slash commands/watchdog status from any channel



Built with 🏴‍☠️ for the Pirates of the Coral-bean hackathon. If this was useful, star the repo and drop a comment — I read every one!

— @ghosthouse7