DevOps Simplified: Easy-to-Use Container Projects Deployment
Tags: devops, docker, automation, tutorial
Published: 2023-12-16 05:37:07


As a developer, we're constantly facing challenges in managing and deploying our projects or applications. Navigating the complexities of making application live on the internet can often seem daunting. In this article, we will explore an easy, streamlined flow for our application deployment, making the process not just manageable, but intuitive and efficient.

Here, we assume you're already familiar with Docker basics and will focus on tools like Docker Compose, Portainer, and Nginx. Ready to streamline our projects from development to live environments? Let's dive in.

## The Main Tools

There are several tools to help managing our projects.

### [Portainer](https://portainer.io)

Portainer is a lightweight management tool that simplifies the way we manage our Docker containers, Docker Swarm, and Kubernetes environments. It provides a user-friendly web interface that allows us to easily manage our Docker host or Swarm cluster. We're using Portainer here to manage our containers stacks.

### [Nginx Proxy Manager](https://nginxproxymanager.com/)

Nginx Proxy Manager is a tools that provide an easy way to manage an Nginx instance as a proxy host to our Application from the live internet. This tools also provide a free SSL using Let's Encrypt if we do not own a custom SSL. This Nginx Proxy Manager will act as our application gateway to the public internet.

# Step By Step

### 1. Setup Git Repo

This git repo will be the manifest for all of our application. We can manage all docker compose file of our application here. We can upload it to private GitHub repo to have all automation versioning bump with [Renovate Bot](https://docs.renovatebot.com)

A manifest repo file tree is 
```
.
├── renovate.json
├── app 1
│   └── docker-compose.yml
└── app 2
   └── docker-compose.yml
```

with the `renovate.json`
```json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recomended",
    "docker:enableMajor",
    ":dependencyDashboard",
  ],
  "prConcurrentLimit": 0,
  "prHourlyLimit": 0,
  "docker-compose": {
    "fileMatch": ["(^|/)(?:docker-)?compose[^/]*\\.ya?ml$"]
  }
}
```
> Follow [Renovate docs](https://docs.renovatebot.com/) for advanced configs e.g: private image registry (gcr, ghcr, etc)

With this manifest repository, we will receive a PR from renovate every update on the docker image tag.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4k760nv7t0qjhy1yysm9.png)

### 2. Create a Docker Networks

Docker network is a feature of docker to define and manage network to connect a container. We have to connect all of our container in one network so that it will be able to communicate to each other.

```bash
docker network create my_network
```

### 3. Spin Up Portainer Instance

To spin up a Portainer instance is simple, all we need is a single `docker-compose.yml` file.

```yaml
version: "3.9"

services:
  portainer:
    image: portainer/portainer-ce:alpine
    container_name: portainer
    privileged: true
    volumes:
      - ./portainer_data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped
    ports:
      - 9443:9443
networks:
  default:
    name: my_network
    external: true
```

Now that the installation is complete, we can log into Portainer dashboard on:
```
https://localhost:9443
```
We might have to replace the localhost with the server IP's.

### 4. Boots up Nginx Proxy Manager

Now we need to run the Nginx Proxy Manager for our reverse proxy. all we need also a single `docker-compose.yml` file.

```yaml
version: "3.9"

services:
  nginx:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx_proxy
    restart: unless-stopped
    ports:
      - 80:80
      - 81:81
      - 443:443
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
networks:
  default:
    name: my_network
    external: true
```

> Follow the [docs](https://nginxproxymanager.com/setup/#running-the-app) for more advanced instruction

There are 3 core ports exposed here. `80` and `443` is the gateway for public HTTP and HTTPS ports. `81` is used for the nginx proxy manager dashboard.

We'll need to log into the nginx dashboard via

```
http://localhost:81
```
Again, we might have to replace the localhost with the server IP's. It generate a default user `admin@example.com` and password `changeme`. You will be asked to modify this at first.


### 5. DNS Setup

After running Portainer and Nginx Proxy Manager, we need to setup our DNS to point to our server. I'm using cloudflare here for the example.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qoghktciamgvh5jzwrap.png)

Setup both desired Nginx and Portainer domain/subdomain.

### 6. Nginx & Portainer domain setup

On the nginx proxy earlier, go to Proxy Host and add a new one

##### Nginx Host

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/luempg7r0xll9s322awm.png)

Insert our domain name we setup earlier on Cloudflare and forward it to `localhost` with the port `81` as the dashboard we currently in are on port 81. Here we can also tell Nginx Proxy Manager to generate a new SSL.


#### Portainer Host

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqil6osi7jd8qnpcbr26.png)

Point this for the container name running Portainer. If you follow my compose file the name is `portainer`, so the hostname will be portainer. This is why we need to create a global docker network as Nginx needs to communicate with the app container.


After saving both of the host, we can access our dashboard by the domain we configure. and from now on, no longer need to use the Server IP's to go to the dashboard.


### 7. Deploying Application

To deploy our application, we need to add it to our manifest repository.
```
.
├── renovate.json
├── nginx
│   └── docker-compose.yml
├── portainer
│  └── docker-compose.yml
└── your application
    └── docker-compose.yml
```

Don't forget to add the Network config on our compose file of the application!

```yaml
networks:
  default:
    name: my_network
    external: true
```

Then we need to add that application on the Portainer dahsboard. Go to environment -> stacks -> add stacks

Fill the name of our stack then choose 'Repository' for the build method. Enter all of the information needed and don't forget to turn on the `GitOps updates`. With this setting, Portainer will send a polling to check updates of the compose file. We don't need to setup manually again, Portainer will automate all the deploying process.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5x3gc9k3zskr2rjbntc3.png)

Deploy the stack when all done.

 
### 8. Seting up Domain for the application

After our container boots up on Portainer, all we need is assigning a domain for our application. Refer to step [5](#5-dns-setup) first to add it on the DNS records. Then we need to configure the proxy host on Nginx Proxy Manager.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suzx0c7chr7cvl4l3gap.png)

---

That's all, it was a bit complicated at first but it is worth the time when there is a version update. All we need is merge the version bump from Renovate Bot and Portainer will spin it up automatically.

## Notable Notes

### Exposed Ports

With the docker network we setup earlier, we no longer need to expose a port on each container. All communication cross-container are handled by the docker Network as long as the container are connected to the same docker network.

We can safely remove the port config on Portainer and Nginx Proxy Manager except the `80:80` and `443:443` as it now become the gateway for all application we have to the public internet.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfhzkwggbf6rdrf2hk0d.png)


## Wrap Up

In this article, we have learned how to streamline an application delivery using docker compose, Portainer and Nginx Proxy Manager. 

It's not a one-size-fits-all solution but rather a practice that can be adapted to our specific needs. Not as powerful as Kubernetes, but this software delivery pipeline are more than enough for small to middle scale application.

---

That's it for this article. Thank you for reading!