Got a new role. Engineering Platform team. Internal tooling, infrastructure automation, developer experience. Customers are engineers.
Team uses Go. No experience. Needed a project. Most HTTP server tutorials stop at basic CRUD. Nothing wrong with that. But I wanted something closer to what platform teams actually build. Decided to make a mini deployment platform. User sends a repo link, platform clones it, builds a Docker image, runs the container. Simple. But enough to touch real infrastructure concepts.
This post covers /deploy.
HTTP server. Was ist das?
Basically, HTTP server receives HTTP requests and sends HTTP responses back. Browser, mobile app, cURL, frontend app, all communicate to server using HTTP. Server exposes routes. Client hits routes. Server handles request and returns response.
Go makes this easy out of the box:
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, req *http.Request) {
res := "hello world"
w.WriteHeader(http.StatusOK)
w.Write([]byte(res))
})
http.ListenAndServe(":8080", nil)
}
And you’re good. Also, try to cURL with:
curl -s -X GET ‘http://localhost:8080/health'
w sends the HTTP response. req is the incoming request. w.WriteHeader sets the status code. w.Write sends the body. Without w.WriteHeader, Go sends it implicitly when you call w.Write.
No explicit method check means the handler is method-agnostic. To restrict:
if req.Method == http.MethodGet {
// Handle GET request
} else {
// Handle other methods or return 405 Method Not Allowed
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
We good on the basics?
User ↓ HTTP Server ↓ Queue ↓ Worker ↓ Docker
User sends a GitHub repo link and number of instances. Server adds it as a job to the queue. Server immediately responds. Worker polls queue every 5s, picks up the job, and handles the actual deployment (clone repo, build Docker image, run container, expose port). Worker updates the DB with deployment status. User can check it later.
Why not just deploy inside the handler?
Deployment takes time. Cloning, building a Docker image, running a container . Can take minutes. Valuable time to watch Netflix. If the handler waits, the request times out. Server wastes the connection holding it open. Bad.
So handler only creates the job. Worker handles everything else in the background. Will take you step by step. But to easily follow the code snippets, see GitHub Link here.
User sends:
type DeploymentRequest struct {
Repository string `json:"repository"`
Instances int `json:"instances"`
}
The json tags tell the decoder which JSON fields map to which struct fields.
Handler:
func (a *App) createDeployment(w http.ResponseWriter, req *http.Request) {
var depReq DeploymentRequest
json.NewDecoder(req.Body).Decode(&depReq)
id, status := a.queue.AddJob(depReq)
job := DeploymentResponse{
ID: strconv.Itoa(id),
Status: status,
}
data := APIResponse{
Code: http.StatusOK,
Message: "service queued",
Data: job,
}
sendResponse(w, data)
}
Queue logic:
func (q *Queue) AddJob(req DeploymentRequest) (int, string) {
q.count += 1
q.jobs[q.count] = NewJob(req.Repository, req.Instances)
return q.count, q.jobs[q.count].Status
}
Queue stores pending jobs. Handler adds. Worker picks up.
Worker:
func (w *Worker) Start() {
for {
jobs := w.queue.jobs
if len(jobs) > 0 {
for k, v := range jobs {
if v.Status == "queued" {
err := w.process(k, v)
if err != nil {
fmt.Printf("error processing job %d", k)
}
time.Sleep(5 * time.Second)
}
}
}
fmt.Println("Waiting...")
time.Sleep(5 * time.Second)
}
}
Worker loops forever. Every 5s, it checks for jobs with queued status and processes them. w.process handles the actual deployment (clone, build, run). After processing, worker updates deployment status in shared state. User can then hit GET /deployments/:id to check.
And in main(), basically:
func main() {
app := NewApp()
worker := NewWorker(app.queue)
go worker.Start()
http.HandleFunc("/deploy", app.createDeployment)
http.ListenAndServe(":8080", nil)
}
Notice go worker.Start(). The go keyword starts the worker in a separate goroutine. Without it, worker blocks the HTTP server entirely. Server never starts. Goroutines are Go's way of running things concurrently. They’re lightweight, cheap, built into the language.
Polling every 5s is inefficient. Real systems use Redis, Kafka, or RabbitMQ instead of a loop. But for understanding the flow, it’s enough.
Learned more about how deployment systems are structured.
Turns out it’s mostly an orchestration problem. Queue jobs. Worker processes them. Infrastructure state changes over time. Everything is async.
Current implementation is rough, I know. In-memory queue, polling worker, messy deployment logic. But the flow is clear:
Next up: real database, actual Docker builds, reverse proxy, deployment logs, health checks. Maybe Kubernetes eventually. And proper queue system.
Technically still CRUD though. Lol.
Full code on GitHub Link (here)[https://github.com/daffafaizan/cspike].