Last weekend, I had the amazing opportunity to be a speaker at AWS Community Day Indonesia 2025. It was one of the most rewarding and eye-opening experiences in my tech journey and in this post, I’d love to share what the day was like, what I learned, and why it meant so much to me. BTW, I shared about “Building a Multi-Tenant Machine Learning Platform on AWS EKS with Ray and JupyterHub”.
Before the Event: Preparation
A few days before the event, to be honest, I felt a mix of excitement and nervousness. Even though I’ve presented in smaller settings before, speaking at a community event like this felt special and a bit more serious!
Here’s what I did to prepare:
- Finalized my presentation topic, making sure the content was practical and relevant for developers and cloud enthusiasts.
- Practiced my talk several times out loud to get comfortable with the flow.
- Set my mindset: I’m not just here to present. I’m here to connect, share, and learn from others.
That mindset shift really helped me calm down before stepping on stage.
During the Event: On Stage and Beyond
When the day finally came, the atmosphere was incredible buzzing with conversations, excitement, and passion for AWS and cloud technologies.
Some of my favorite moments were:
- Meeting so many people from the AWS community, many of whom I had only interacted with online before.
- Taking the stage to deliver my session “Building a Multi-Tenant Machine Learning Platform on AWS EKS with Ray and JupyterHub”.
- Even though I was nervous at first, once I started talking, everything clicked. It didn’t feel like a “presentation” anymore it felt like a conversation with friends who shared the same curiosity and enthusiasm.
Key Takeaways & Reflections
Looking back, here are a few lessons that really stuck with me:
-Community is everything. Behind every technology, it’s always about people learning, sharing, and growing together.
-Preparation builds confidence. The more I practiced, the more natural it felt on stage.
-Sharing is a two-way process. I came to give a talk, but I left with more insights from the audience than I expected.
-Stay adaptable. Every crowd is different. Reading the room and adjusting how you explain something is an important skill.
-Keep growing. This experience reminded me that growth doesn’t just happen behind the keyboard it happens when you step out, speak, and share what you’ve learned.
What’s Next?
To be honest, there are still many aspects I haven’t fully covered in my presentation. After this event, I plan to expand this talk into a deeper technical series exploring the end-to-end ML lifecycle using Ray on AWS.
While this session mainly focused on the infrastructure layer, there’s so much more to uncover about how Ray can power the entire machine learning pipeline from data preprocessing and distributed training with Ray Train, to hyperparameter tuning with Ray Tune, and serving models in production with Ray Serve.
Hello there! In this post, we’ll dive into the world of Caddy, a modern and powerful web server. Built using Go, Caddy offers a range of built-in features, including reverse proxy and load balancing.
By the way, in this project, I’ll use Caddy 2, the latest version, which comes with a completely rewritten architecture and enhanced features. To make our setup and testing as smooth as possible, we’ll leverage Docker. Docker allows us to create a reproducible environment with all the necessary components easily.
The main focus of this post will be on understanding and experimenting with Caddy’s load balancing algorithms. We’ll explore how Caddy intelligently distributes incoming traffic, the different strategies it offers (such as Round Robin, Least Connection, Random, and more), and how you can configure the for various use cases.
By using Docker, we’ll quickly spin up multiple backend services (which are called “workers”) and route requests through a single Caddy instance acting as our load balancer.
The Project Setup
To get started, let’s look at the project structure. Our setup is straightforward, allowing us to focus on the core concepts of load balancing.
Here’s the project structure:
Terminal window
├──caddy
│└──Caddyfile
├──docker-compose.yml
└──src
├──Dockerfile
├──go.mod
└──main.go
Our setup is composed of three main parts:
1.Backend Workers (src): These are simple Go applications the will handle the requests. Each worker will simply return a “Hello form [hostname]” message, allowing us to sess which server is handling the request. This is the perfect way to visualize how Caddy distributes the load.
2.Caddy Load Balancer (caddy): Our Caddy instance will act as the reverse proxy, forwarding requests to the worker services. We’ll use the Caddyfile to define our load balancing rules.
3.Docker Compose (docker.compose.yml): This file orchestrates everything, defining and running our multi-container application with a single-command.
Understanding the Components
Let’s break down the files in our project.
src/main.go
This is a simple Go HTTP server. It listens on port 8081 and responds to any request by printing its hostname. This is our primary tool for observing the load balancing behavior.
package main
import (
"fmt"
"net/http"
"os"
)
funchandler(w http.ResponseWriter, r *http.Request) {
hostname, _:= os.Hostname()
fmt.Fprintf(w, "Hello from %s\n", hostname)
}
funcmain() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8081", nil)
}
src/Dockerfile
This Dockerfile builds our Go application into a lightweight, self-contained Docker image.
FROM golang:1.24 AS builder
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM golang:1.24-alpine
COPY --from=builder /go/bin/app /
EXPOSE 8081
ENV PORT 8081
CMD ["/app"]
docker-compose.yml
This file ties everything together. We define a service for our Caddy load balancer and three worker services.
version: "3.8"
services:
load_balancer:
image: caddy:2.10-alpine
container_name: load_balancer
ports:
- "8082:80"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
worker_1:
build: ./src
container_name: worker_1
hostname: worker_1
expose:
- "8081"
worker_2:
build: ./src
container_name: worker_2
hostname: worker_2
expose:
- "8081"
worker_3:
build: ./src
container_name: worker_3
hostname: worker_3
expose:
- "8081"
caddy/Caddyfile
This is where the magic happens! The Caddyfile is Caddy’s configuration file. We’ll define a reverse proxy that routes to our workers. The lb_policy directive is where we’ll specify our load balancing algorithms.
Now that our project is set up, we can start experimenting. To run the project, simply execute docker-compose up in your terminal. You can then send requests to http://127.0.0.1:8082 and observe which worker responds.
1. Round Robin (lb_policy round_robin)
This is the most common and simplest load balancing algorithm. Caddy distributes incoming requests to the backend servers in a sequential, rotating manner. It’s a fair and predictable method, assuming all servers are equally capable of handling the load.
How to Configure:
Modify your caddy/Caddyfile to use the round_robin policy.
After running docker-compose up -d --build, open your terminal and send a few requests using curl. You should see that Caddy distributes the traffic evenly among the three workers.
Terminal window
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_2
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_3
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
Now, let’s test Caddy’s fault tolerance. In a real-world scenario, a server might crash or become unresponsive. We’ll simulate this by manually stopping one of the workers.
Run the following command in your terminal to stop worker_1:
Terminal window
$dockerstopworker_1
After a few moments (the health_interval you set, e.g., 3 seconds), Caddy will perform its next health check, detect that worker_1 is unresponsive, and automatically mark it as unhealthy.
Now, send a few more requests. What do you expect to happen? With worker_1 down, Caddy should intelligently stop routing traffic to it and redirect all requests to the remaining healthy servers (worker_2 and worker_3).
2. Weighted Round Robin (lb_policy weighted_round_robin)
This algorithm is a more advanced version of Round Robin. It allows you to assign a “weight” to each backend server, which determines its share of the requests. Servers with a higher weight will receive more traffic than those with a lower weight. This is ideal when you have servers with varying capacities, for example, a new, more powerful server and an older, less powerful one.
You can also use this policy to gradually drain traffic from an old server or ramp up traffic to a new one during deployments, making it a very useful strategy.
How to Configure:
To use this policy, you need to add the weight to each server’s address in the Caddyfile. For our example, let’s give worker_1 a higher weight of 3, while worker_2 and worker_3 each have a weight of 1. This means worker_1 should handle three out of every five requests.
After updating the Caddyfile, make sure to reload or restart your Caddy container to apply the changes. You can do this with docker-compose up -d --build.
How to Test:
Now, let’s send a few requests to our load balancer and see how Caddy distributes the traffic according to the assigned weights. Send a few requests using curl and observe the responses.
Terminal window
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_2
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_3
$curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
3. Least Connection (lb_policy ip_hash)
Aight, let’s dive into Least Connection. Unlike Round Robin, which is a simple, sequential algorithm, Least Connection is a dynamic and more intelligent load balancing policy. It chooses the backend server with the fewest number of currently active requests. This policy is excellent for situations where your requests have a highly variable processing time.
For example, if one of your servers gets a handful of complex, long-running requests while the others are handling many small, quick ones, this algorithm will automatically route new traffic to the servers that are less burdened, preventing a single server from becoming a bottleneck. If there’s a tie, meaning two or more servers have the same lowest number of connections, Caddy will randomly choose one of them.
How to Configure:
Configuring this policy is simple. You just need to change the lb_policy directive in your Caddyfile.
After updating your Caddyfile, make sure to restart your Caddy container with docker-compose up -d --build to apply the changes.
How to Test:
To demonstrate the Least Connection algorithm, you’ll need to modify your Go code to simulate a long-running request. This will allow you to see how Caddy intelligently routes traffic away from the busy worker.
-Update Your Go Code
Open your src/main.go file and add a new handler that will simulate a task with a significant delay. This will act as our “long-running request.”
package main
import (
"fmt"
"net/http"
"os"
"time"
)
funchandler(w http.ResponseWriter, r *http.Request) {
hostname, _:= os.Hostname()
fmt.Fprintf(w, "Hello from %s\n", hostname)
}
funclongHandler(w http.ResponseWriter, r *http.Request) {
hostname, _:= os.Hostname()
time.Sleep(10* time.Second)
fmt.Fprintf(w, "Hello! long running request finished from %s\n", hostname)
}
funcmain() {
http.HandleFunc("/", handler)
http.HandleFunc("/long", longHandler)
http.ListenAndServe(":8081", nil)
}
-Rebuild and Run Docker Compose
After updating your code, you must rebuild and run your containers to apply the changes.
Terminal window
$docker-composeup-d--build
-Test the Scenario
Now, you can test the Least Connection algorithm using two separate terminals.
Terminal 1 (Long-Running Request):
Start a request to the /long endpoint. This will open a connection to one of the workers and hold it for 10 seconds. Caddy will detect that this worker has an active, ongoing connection.
Terminal window
$curlhttp://127.0.0.1:8082/long
Terminal 2 (Normal Requests):
Immediately after running the command in Terminal 1, switch to Terminal 2 and send several quick requests to the root endpoint (/).
Terminal window
$curlhttp://127.0.0.1:8082/
# Output: Hello from worker_2
$curlhttp://127.0.0.1:8082/
# Output: Hello from worker_3
$curlhttp://127.0.0.1:8082/
# Output: Hello from worker_2
You will observe that Caddy will not send requests to the worker that is currently busy with the long-running request. Instead, all new requests will be routed to the other two workers, demonstrating how the least_conn algorithm effectively balances load dynamically. Once the long-running request is complete, that worker will once again be available to handle new requests.
4. IP Hash (lb_policy ip_hash)
The IP Hash load balancing algorithm is different from the previous ones because it’s focused on session persistence. Instead of distributing requests based on a sequential or random order, it creates a hash from the client’s IP address and uses that hash to consistently route all requests from that same client to the same backend server.
How to Configure:
Configuring the IP Hash policy is straightforward. You simply need to replace the lb_policy directive in your Caddyfile with ip_hash.
After updating your Caddyfile, make sure to restart your Caddy container with docker-compose up -d --build to apply the changes.
How to Test:
To test this algorithm, you’ll need to send requests from different “clients” (i.e., different IP addresses) and observe where they are routed. The easiest way to simulate this is by sending requests from your local machine and then using a proxy or a different network to see if the requests are routed to a different server.
No matter how many times you run curl from the same machine, the requests will always be routed to the same worker. This is because Caddy is hashing your local IP address (127.0.0.1 or the container’s internal IP) and consistently mapping it to that specific worker.
This demonstrates how IP Hash ensures session stickiness without needing to share session data across all servers. It’s a powerful tool for maintaining a consistent user experience.
5. Random (lb_policy random)
The Random load balancing policy is the simplest and most unpredictable of all the algorithms. As its name suggests, it selects a backend server at random for each new request. There is no sequential pattern or special logic; every request has an equal chance of being routed to any of the available servers.
While it may seem less sophisticated than other algorithms, the Random policy is surprisingly effective in many scenarios. It’s fast, has a very low overhead, and can be a great choice for distributing traffic evenly across a large pool of homogenous servers. It naturally avoids the “thundering herd” problem that can sometimes occur with Round Robin on first-come-first-served requests, as it prevents all clients from hitting the same server at the same time.
How to Configure:
Configuring this policy is the easiest. Simply replace the lb_policy directive in your Caddyfile with random.
After updating your Caddyfile, make sure to restart your Caddy container with docker-compose up -d --build to apply the changes.
How to Test:
To test the Random policy, send a series of quick requests and observe the output. Unlike the predictable pattern of Round Robin or the consistent output of IP Hash, the responses will come from different workers in an unpredictable order.
Terminal window
curlhttp://127.0.0.1:8082
# Output: Hello from worker_3
curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
curlhttp://127.0.0.1:8082
# Output: Hello from worker_2
curlhttp://127.0.0.1:8082
# Output: Hello from worker_1
Conclusion
Throughout this post, we’ve explored the core capabilities of Caddy as a powerful web server and a flexible load balancer. Using a simple Docker setup, we were able to quickly demonstrate five different load balancing algorithms, each with its own unique advantages:
-Round Robin: The classic, simple approach for evenly distributing traffic in a predictable sequence.
-Weighted Round Robin: A smarter version of Round Robin that allows you to prioritize traffic to more powerful servers.
-Least Connection: A dynamic algorithm that routes traffic based on real-time load, preventing a single server from becoming a bottleneck.
-IP Hash: The ideal choice for session stickiness, ensuring a consistent user experience by always routing a client to the same backend server.
-Random: A straightforward and fast algorithm for scattering traffic across servers, effective for a large pool of homogenous workers.
This hands-on experience proved that Caddy is not just a simple web server but a robust tool for building scalable and reliable applications. Caddy’s elegant syntax and powerful features make it an excellent choice for anyone looking to simplify their server configurations without sacrificing control or performance. Whether you’re a seasoned developer or just starting, Caddy offers a smooth and intuitive experience that can handle everything from a single website to a complex, distributed application.
Hello! In this post, I’ll walk through how to use Caddy as a reverse proxy and Docker for containerization to deploy a simple Go API. This method offers a quick and modern way to get your Go API up and running.
Before we dive into the deployment steps, let’s briefly discuss why Docker and Caddy are an excellent combination.
- Docker is a containerization platform that packages your app and all its dependencies into an isolated unit. This guarantees that your app runs consistently everywhere, eliminating the classic “it works on my machine” problem.
- Dockerile is the blueprint that defines how your Docker image is built. It specifies the base image, the steps to compile your application, and how the container should run.
- Docker Compose is a tool for defining and running multi-container Docker applications. Instead of starting each container manually, we can describe the entire stack in a single YAML file.
- Caddy is a modern reverse proxy and web server built using Go. Caddy is renowned for its ease of use, especially its automatic HTTPS feature. Its simple configuration makes it an ideal choice for serving our API.
Requirements
Before we start, you’ll need to install Go, Docker and Docker Compose on your system.
1. Go
Make sure Go is installed. You can check your version with:
$ go version
go version go1.24.0 linux/amd6
If it’s not installed, you can download it from the official site.
2. Docker & Docker Compose
Make sure Docker & Docker Compose are installed. You can check your docker version with:
Terminal window
$docker--version
Dockerversion27.5.1,build9f9e405
Terminal window
$dockercomposeversion
DockerComposeversionv2.3.3
Follow the official guides to install Docker and Docker Compose for your operating system if you haven’t installed them before. The official site.
Setting Up the Project
Step 1: Create and Run a Simple Go API
First, create a new directory for the project and initialize a Go module:
Terminal window
$mkdirgo-api && cdgo-api
$ go mod init example.com/go-api
This command creates a Go module (go.mod) named example.com/go-api, which helps manage dependencies and makes the project reproducible.
- Next, create a new file main.go and define a simple HTTP server using Chi as the router.
The server exposes two routes:
1. Root path / -> return an ASCII banner generated with the go-figure package.
funchandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
asciiArt:= figure.NewFigure("Go API - Caddy", "", true).String()
fmt.Fprintln(w, asciiArt)
}
2./api/hello -> returns a simple JSON response.
funcapiHandler(w http.ResponseWriter, r *http.Request) {
We’ll use a multi-stage build to create a minimal and secure Docker image. This process compiles our Go application in one stage and then copies only the final binary to a much smaller final image. This keeps our final image size low.
Create a Dockerfile in your project directory and add the following code:
# First stage: builder
FROM golang:1.24 AS builder
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
- In the builder stage, we use the official Go image to compile our code.
- We copy the entire project into the container and run go mod download to fetch dependencies.
- Then we build the binary with CGO_ENABLED=0 to ensure it’s statically compiled and portable. The binary is placed in /go/bin/app.
# Second stage: final image
FROM golang:1.24-alpine
# Copy only the compiled binary from builder stage
COPY --from=builder /go/bin/app /
EXPOSE 8081
ENV PORT 8081
CMD ["/app"]
- In the final stage, only the compiled binary is copied over from the builder stage. This keeps the image small because source code, dependencies, and build tools are excluded.
- We expose port 8081 so Docker knows which port the app listens on.
-CMD ["/app"] runs the binary as the container’s main process.
Here the full code:
FROM golang:1.24 AS builder
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM golang:1.24-alpine
COPY --from=builder /go/bin/app /
EXPOSE 8081
ENV PORT 8081
CMD ["/app"]
Step 3: Configure Caddy
Caddy will act as a reverse proxy that forwards incoming requests to the Go API container. This allows us to:
Tells Caddy to listen on port 80 (HTTP).
Forwards requests to the Go API container, using the Docker service name go-api and port 8081.
Create a file named Caddyfile in your project directory:
Terminal window
:80{
reverse_proxygo-api:8081
}
đź’ˇ If you later have a domain (e.g. api.example.com), you can replace :80 with your domain
Terminal window
api.example.com{
reverse_proxygo-api:8081
}
Step 4: Configure Docker Compose
Create a file named docker-compose.yml in your project directory:
version: "3.8"
services:
go-api:
build: .
container_name: go-api
expose:
- "8081"# Expose port internally (only visible to other services in this network)
restart: unless-stopped
caddy:
image: caddy:2.10-alpine
container_name: caddy
ports:
- "8082:80"# Map host port 8082 -> container port 80
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
restart: unless-stopped
depends_on:
- go-api
Explanation
go-api service
- Built from your local Dockerfile.
- The Go API will listen on :8081, but only Caddy can access it.
-ports: "8082:80" -> exposes port 80 from the container to port 8082 on your host machine. So when you open http://127.0.0.1:8082, requests are routed through Caddy.
- The Caddyfile is mounted so you can configure reverse proxy behavior.
-depends_on ensures Caddy waits for the Go API container to start.
Step 5: Test the Setup
Once all files are ready (main.go, Dockerfile, Caddyfile, docker-compose.yml), run the stack using docker compose up command:
Terminal window
├──Caddyfile
├──docker-compose.yml
├──Dockerfile
├──go.mod
├──go.sum
└──main.go
Terminal window
$dockercomposeup-d--build
Make sure the containers are running using docker ps command:
Terminal window
$dockerps
CONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTS
NAMES
6b6b35487d77caddy:2.10-alpine"caddy run --config …"9minutesagoUp9minutes443/tcp,2019/tcp,443/udp,0.0.0.0:8082->80/tcp, [::]:8082->80/tcp caddy
Now, test the endpoints through Caddy (reverse proxy):
Terminal window
$curlhttp://127.0.0.1:8082
Expected output: the ASCII art rendered by go-figure.
Terminal window
$curlhttp://127.0.0.1:8082/api/hello
Expected output:
Terminal window
{"message":"Hello, World!"}
Conclusion
In this guide, we successfully deployed a simple Go API containerized approach. By leveraging Docker, we created an isolated and reproducible environment for our application. We used a multi-stage build in our Dockerfile to keep the final image lightweight and secure, and Docker Compose to easily manage both our Go API and Caddy services with a single command.
Feel free to explore further by adding more features to your API or by setting up a domain with Caddy’s automatic HTTPS. Happy deployment!🚀
Hi there! Flask is great for building APIs quickly. But turning your local project into a publicly accessible web service involves a few extra steps that aren’t always obvious.
In this guide, I’ll show you how to deploy a Flask API using Gunicorn as the WSGI server, Supervisor to manage the process, and Nginx as a reverse proxy.
Overview
-Flask: The Python microframework we’ll use to build the API.
-Gunicorn: A Python WSGI HTTP server for running Flask in production.
-Supervisor: A process control system to ensure the Gunicorn server stays alive.
-Nginx: A reverse proxy to handle client requests and route them to Gunicorn.
Project Flow
The diagram below illustrates the flow of a request and response when using Flask, Gunicorn, Supervisor, and Nginx.
When a user sends an HTTP request, it first reaches the Nginx reverse proxy. Nginx forwards the request to Gunicorn, which serves the Flask application via the WSGI protocol. Supervisor ensures that Gunicorn keeps running and automatically restarts it if needed. The response follows the same path back to the user.
Requirements
Before starting, make sure you have the following installed on your system:
- Python 3 and Virtualenv
Check if Python is installed:
Terminal window
$python3--version
Python3.10.14
If not installed, install it:
Ubuntu/Debian:
Terminal window
$sudoaptupdate
$sudoaptinstallpython3python3-venv-y
CentOS/RHEL:
Terminal window
$sudoyuminstallpython3python3-venv-y
Homebrew (macOS):
Terminal window
$brewinstallpython
- Nginx
Ubuntu/Debian:
Terminal window
$sudoaptinstallnginx-y
CentOS/RHEL:
Terminal window
$sudoyuminstallnginx-y
Homebrew (macOS):
Terminal window
$brewinstallnginx
After installation, check if Nginx is running:
Terminal window
$sudosystemctlstatusnginx
If it’s not running, start and enable it:
Terminal window
$sudosystemctlstartnginx
$sudosystemctlenablenginx
Supervisor
Ubuntu/Debian:
Terminal window
$sudoaptupdate
$sudoaptinstallsupervisor-y
CentOS/RHEL:
Terminal window
$sudoyuminstallsupervisor-y
Homebrew (macOS):
Terminal window
$brewinstallsupervisor
After installation, check if Supervisor is running:
Terminal window
$sudosystemctlstatussupervisor
If it’s not running, start and enable it:
Terminal window
$sudosystemctlstartsupervisor
$sudosystemctlenablesupervisor
Setting Up the Flask Project
First, create a project directory and set up a virtual environment:
Terminal window
$mkdirflask-api && cdflask-api
Terminal window
$python3-mvenvvenv
$sourcevenv/bin/activate
With the virtual environment activated, install Flask and Gunicorn:
Terminal window
$pipinstallflaskgunicorn
You can verify the installation:
Terminal window
$flask--version
$gunicorn--version
Next, you need to create a file called app.py inside your project directory. You can use any text editor you prefer, such as nano, vim, or others:
Terminal window
$vimapp.py
fromflaskimportFlask
app=Flask(__name__)
@app.route("/api/hello")
defhello():
return{"message":"Hello from Flask API!"}
Then, try to run your Flask app using Gunicorn command:
Finally, ff everything is set up correctly, you should now be able to access your Flask API at http://YOUR_SERVER_IP/api/hello if you’re using a public server or VPS.
Optional: Project Structure & Requirements
To easily reinstall dependencies later:
Terminal window
$pipfreeze>requirements.txt
However, it’s better to specify only the packages you need manually:
Terminal window
$sudovimrequirements.txt
Terminal window
flask
gunicorn
To install all requirements, just run pip install:
Terminal window
$pipinstall-rrequirements.txt
Your project structure should look like this:
Terminal window
flask-api/
├──app.py
├──venv/
└──requirements.txt
Common Issues
Here are a few quick troubleshooting tips:
502 Bad Gateway: Usually means Gunicorn isn’t running or the Nginx config has the wrong port.
Supervisor status shows STOPPED: Check your config file paths and the logs: sudo tail -f /var/log/flask-api.err.log
Permission errors: Ensure all paths used by Supervisor and Gunicorn are accessible by the appropriate user.
Conclusion
In this guide, we deployed a Flask API using Gunicorn as the WSGI server, Supervisor to keep the app running reliably, and Nginx as a reverse proxy to handle incoming requests. With this setup, your Flask app is ready to serve real traffic efficiently and automatically recover from crashes. Thanks for reading — and good luck with your deployment! 🚀
Hi! In this post, I’ll show you how to deploy a Go API using Supervisor to manage the process and Nginx as a web server to serve it.
Before we dive into the deployment steps, let’s briefly discuss why we’re using Supervisor and Nginx.
- Supervisor is a process control system that helps manage and monitor applications running in the background. It ensures that your Go API stays up and automatically restarts it if it crashes. See the full documentation
- Nginx is a high-performance web server that can also function as a reverse proxy, making it ideal for serving our Go API to the internet. See the full documentation
🤔 Why Choose Supervisor Over Other Options?
You might wonder why we use Supervisor instead of alternatives like Systemd, PM2, or containerized solutions like Docker. Here’s a quick comparison:
Tools
Pros
Cons
Supervisor
Simple setup, great for managing multiple processes, easy log management
Requires manual config
Systemd
Native to Linux, faster startup
More complex setup, harder to debug
PM2
Built for Node.js, supports process monitoring
Not ideal for Go applications
Docker
Isolated environment, easy deployment, scalable
More setup overhead, requires container knowledge
When Should You Use Supervisor?
Use Supervisor when you want a non-containerized way to manage a Go service, with features like auto-restart and log management, without dealing with systemd’s complexity or Docker’s extra overhead.
Setup and Run a Simple Go API
Requirements
Before starting, make sure you have the following installed on your system:
Server’s Public IP (if running on a VPS or remote server)
Terminal window
curlhttp://YOUR_SERVER_IP
Note: If you want to access your Go API using a custom domain instead of an IP address, you need to purchase a domain, configure its DNS to point to your server’s IP, and update your Nginx configuration accordingly. For better security, it’s recommended to set up HTTPS using Let’s Encrypt.
Conclusion
In this guide, we deployed a Go API using Supervisor to manage the process ensuring automatic restarts and efficient request handling also Nginx as a reverse proxy. Thank you for reading, and good luck with your deployment! 🚀