DEV Community

Cover image for Run a Golang, Nginx, and React App in Docker
Alex Awesome
Alex Awesome

Posted on • Edited on

Run a Golang, Nginx, and React App in Docker

Did you try to run your Golang, Nginx, and React apps locally and fight compatibility issues?
You need docker to save time. If you are very brave you can even use this scheme in production. But you will be judged by mature programmers with long beards.
In this article, we'll guide you through the process of running your Golang, Nginx, and React app in a Docker container, so you can avoid the headache of managing multiple environments.
I guess you know about docker if you can find this article.
Don't worry, you don't need to be a Docker expert or a rocket scientist to follow along.

Let's get started!

Setting Up the Environment:

Install the docker if you haven't done it yet.

We also will use Docker Compose. You have to have it. Don't be scared it's very easy to start using.

Next, create a new directory on your machine to hold your app files. Let's call it «docker-me-up-baby».

mkdir docker-me-up-baby
cd docker-me-up-baby
Enter fullscreen mode Exit fullscreen mode

Now, create «docker-compose.yml». This file will define the services that make up your app and their configurations. Here's an example docker-compose.yml file for our simplest application:

version: '3'
services:
  web:
    build: ./nginx
    ports:
      - "80:80"
    depends_on:
      - api
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    depends_on:
      - api

Enter fullscreen mode Exit fullscreen mode

What the hell is going on here 👆?

This docker-compose.yml file does a few things:

  • It defines four services: web (for Nginx), api (for Golang), db (for PostgreSQL), and frontend (for React).
  • It specifies the build context for the api and frontend services, which tells Docker Compose where to find their Dockerfiles.
  • It exposes port 80 on the web service to port 80 on the host machine.
  • It exposes port 3000 on the frontend service to port 3000 on the host machine.

That's it for setting up the environment with Docker Compose! Now, you can build and run your app with a single command. Docker Compose takes care of building the images, creating the containers, and linking them together. All you need to do is run:

docker-compose up

Enter fullscreen mode Exit fullscreen mode

I guess it won't work right now but believe me it's the right command.

Docker Compose will start all the services defined in the docker-compose.yml file, and you will access your app by visiting http://localhost in your browser.

Building and dockerizing the simplest Golang API:

Let's implement our rocket-science API

// main.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World!")
    })

    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

It's great, isn't it?!

Lets dockerize this one (I'm assuming that Golang part is in the root directory of our project):

# Official Golang image (You shouldn't use the `latest` version in production but I'm a bad guy)
FROM golang:latest

# Working directory
WORKDIR /app

# Copy everything at /app
COPY . /app

# Build the go app
RUN go build -o main .

# Expose port
EXPOSE 8080

# Define the command to run the app
CMD ["./main"]

Enter fullscreen mode Exit fullscreen mode

You can understand what is happening in this file by reading the comments.
Let's repeat one more time:

  • Dockerfile gets the official Golang image.
  • Sets the working directory to /app.
  • Copies the contents of the current directory into the /app directory in the container.
  • Builds the go app using the go build command.
  • Exposes port 8080.
  • Defines the command to run the app (in this case, ./main).

To build the Docker image for this Golang API, we cat run the following command in the app's directory (but we won't):

docker-compose build api
Enter fullscreen mode Exit fullscreen mode

Actually, we don't need to do it because we will build everything everywhere all at once later.

We will do the same with running our container. I just have to mention that you can use this command for running the container

docker-compose up api
Enter fullscreen mode Exit fullscreen mode

But we don't need it right now.

If you are so impatient, you can run build and run commands above. You can be sure that everything is ok if you see the "Hello world!" console. You can also access the API by visiting http://localhost:8080.

That's it for building the Docker image for the simplest Golang API!

Let's go ahead.

Set up the Nginx server (in Docker of course)

Let's put Nginx files to a new directory named nginxinside the app's directory. Inside the nginx directory, create a new file named nginx.conf with the following configuration:


server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://api:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Enter fullscreen mode Exit fullscreen mode

This configuration sets up an Nginx server to listen on port 80 and forward all requests to the Golang API running on port 8080. Why is "api" in http://api:8080? It's name of the Golang API service defined in the docker-compose.yml file.

To build the Nginx image in a Docker container, create a new file named "Dockerfile" inside the "nginx" directory:

# Official Nginx image (Yes, in this article I always use the `latest`. Kill me!)
FROM nginx:latest

# Copy Nginx configuration file to the container
COPY nginx.conf /etc/nginx/conf.d

# Expose port 80
EXPOSE 80
Enter fullscreen mode Exit fullscreen mode

One more time:

  • This Dockerfile gets the official Nginx runtime image as the parent image.
  • Copies the Nginx configuration file to the container.
  • Exposes port 80.

For build and run you can use the same commands as for the Golang API container but with name of service web.

docker-compose build web
docker-compose up web
Enter fullscreen mode Exit fullscreen mode

If you run golang and nginx services by docker-compose up API web you can check the http://localhost in the browser.

Create the React app.

This article is not about building a React application. We can use something simple (From root project directory)

npx create-react-app frontend
cd frontend
Enter fullscreen mode Exit fullscreen mode

Add the API integration:

Add the stupidest API integration in all over the world.
Create a new file named "api.js" in the "frontend/src" directory:

const api = {
  async getHello() {
    const response = await fetch('/api');
    const data = await response.json();
    return data.message;
  }
}

export default api;

Enter fullscreen mode Exit fullscreen mode

For development, we will use the Docker.

frontend/Dockerfile:

# Use a Node.js image
FROM node:latest

# Set the working directory
WORKDIR /app

# Copy the package.json and package-lock.json files
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy everything to the container
COPY . .

# Expose port
EXPOSE 3000
Enter fullscreen mode Exit fullscreen mode

This Dockerfile

  • Gets the Node.js image.
  • Sets the working directory to /app.
  • Copies the package.json and package-lock.json files to the container.
  • Installs dependencies
docker-compose build frontend
docker-compose up frontend
Enter fullscreen mode Exit fullscreen mode

You can see something at http://localhost:3000

Let's simulate production build

It's just a demonstration. It's the worst way for real-life applications. Anyway, build the build

npm run build
Enter fullscreen mode Exit fullscreen mode

Add the React build files to your Nginx configuration.

To include the React build files in your Nginx server, let's make nginx.conf file like this:

server {
    listen 80;
    server_name localhost;

    location / {
        root /app/frontend/build;
        try_files $uri /index.html;
    }

    location /api {
        proxy_pass http://api:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Enter fullscreen mode Exit fullscreen mode

This configuration sets up an Nginx server to serve the React app from the build directory and forwards all API requests to the Golang API running on port 8080.

And finally, let's add a little correction to the docker-compose:

version: '3'
services:
  web:
    build: ./nginx
    ports:
      - "80:80"
    depends_on:
      - api
    volumes:
      - frontend/build:/app/frontend/build
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    depends_on:
      - api

Enter fullscreen mode Exit fullscreen mode

Got it? We added volumes. It will mount our frontend/build directory to the Nginx container.

All together

Now that you've built the Docker images for your Golang API, Nginx server, and React app, it's time to run the Docker container and test your app.

Start the Docker container:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

You can use

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

to run it in the background

Test the app:

To test your app, visit http://localhost in your browser. You should see your React app being served by the Nginx server, with API requests being forwarded to the Golang API.

You can also test your Golang API by visiting http://localhost/api in your browser. You should see a JSON response with a "message" field containing the text "Hello, world!".

Stop it!

Stop the Docker container:
To stop the Docker container, press Ctrl + C in your terminal or run the following command if you use -d option:

docker-compose down
Enter fullscreen mode Exit fullscreen mode

This will stop and remove all the Docker containers for your app.

That's it! You've successfully built and run a Docker container for your Golang, Nginx, and React app. Docker Compose makes it easy to manage all the services in your app and ensure they work together correctly.

--

If you want to learn more about Docker and app deployment, here are some additional resources you can check out:

  • Docker's official documentation: Docker's documentation provides detailed information on how to use Docker and Docker Compose to build and run containers. It includes guides, tutorials, and reference materials for beginners and advanced users.
  • Docker Hub: Docker Hub is a public registry of Docker images, where you can find images for various services and software. You can also use Docker Hub to store and share your own images.
  • Docker Stack is a tool provided by Docker that allows you to deploy a multi-container Docker application on a Docker Swarm cluster. It enables you to define your application's services, networks, and volumes in a single file called a "Compose file" and deploy the entire stack to the swarm.
  • Kubernetes: Kubernetes is an open-source container orchestration system that automates the deployment, scaling, and management of containerized applications. It can be used to deploy and manage Docker containers in a production environment.

I don't post links on purpose. Technology development is very fast. Find actual ones on Google.

-

Follow me on Telegram, and you can read all my articles https://t.me/awesomeprog.

Top comments (2)

Collapse
 
bherbruck profile image
bherbruck

Can you serve the frontend files in the golang server and simplify the deployment to one dockerfile? Maybe even one service?

Collapse
 
shaggyrec profile image
Alex Awesome

Sure.
This is a bit synthetic example that is stuck between development and production.
For non-development purposes, we can build a frontend once and share it with the nginx service. For example, we can do it using Giltab artifacts.
Nginx is here as the most popular web-server.
We actually could serve all things through the Golang but I know nobody who does it that way.
For development, it probably could make sense but I prefer to follow the Separation of concerns