Deploying a Medusa + Minio + MeiliSearch stack with Docker and Traefik
Tags: medusa, traefik, meilisearch, minio
Published: 2023-03-05 19:26:02

Deploying a production-ready Medusa stack with a proper search engine and a cloud storage can be quite challenging, especially for beginners. However, with a few simple steps, you can deploy your own stack and have it up and running in no time. In this blog post, I will guide you through the process and provide you with some tips for troubleshooting.

## Table of contents
* [Components of the stack](#components-of-the-stack)
* [Requirements](#requirements)
* [Setup your hosting and (sub)domains] (#setup-your-hosting-and-sub-domains)
* [Setup Medusa stack] (#setup-medusa-stack)
* [Troubleshooting] (#troubleshooting)

## Components of the stack

* [Medusa Starter Default](https://github.com/medusajs/medusa-starter-default)
* [Minio Cloud Storage](https://docs.medusajs.com/add-plugins/minio)
* [MeiliSearch](https://docs.medusajs.com/add-plugins/meilisearch/)
* [Stripe](https://docs.medusajs.com/add-plugins/stripe)
* ✨ SSL certificate via CloudFlare + LetsEncrypt

## Requirements

* A VPS with at least 1GB of RAM. 
* Docker and Docker Compose.
* A custom domain pointing to CloudFlare.
* A CloudFlare account.

## Setup your hosting and (sub)domains

After getting access to your VPS you need to install and configure Docker and Docker compose. 

Once you configure your server we are going to setup our domain. We will use CloudFlare to manage our domain's DNS records and handle the validation of a free SSL LetsEncrypt certificate [in a easy way](https://doc.traefik.io/traefik/v1.6/configuration/acme/).

1. Follow the instructions to [add your site](https://developers.cloudflare.com/fundamentals/get-started/setup/add-site/) to your CloudFlare's account.
2. Setup each A record to point your VPS IP address.
![DNS A Records](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sy18ywuqh3wy59ven8x1.png)
3. Set your SSL/TLS encryption mode to "Full (strict)".
![SSL/TLS encryption mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8xp16xicsd22w2200vwd.png)
4. Obtain your [CloudFlare's API tokens](https://dash.cloudflare.com/profile/api-tokens).

![CloudFlare API tokens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7ksk8uxorh2xghh56kr.png)

Write down those codes, we are going to use them in following steps.

> NOTE: Free TLDs like **.ga**, **.ml** or **.tk** [won't work with the automatic DNS validation](https://community.cloudflare.com/t/unable-to-add-free-domains-to-cloudflare-tk-ml-ga-cf-gq/303502/2).


## Setup Medusa stack
Once you installed Docker and secure your VPS, is time to set up the Medusa stack:

### Step 1: Clone the Github repository.
You can clone the Github repository using the following command:

```bash
git clone https://github.com/beakman/medusa-stack-dockerized.git
```

### Step 2: Edit the `.env` file.
In the root directory of the cloned repository, you will find a file named `.env.example`. You should copy this file and rename it to `.env`. Open the `.env` file and edit the values to suit your environment.

#### CloudFlare configuration
```bash
# LetsEncrypt CloudFlare verification
EMAIL=user@example.com
CERT_RESOLVER=letsencrypt
CLOUDFLARE_EMAIL=
CLOUDFLARE_API_KEY=
CLOUDFLARE_DNS_API_TOKEN=
```
Here you should provide the previously generated API token and your CloudFlare's email.

#### Medusa server
```bash
MEDUSA_DOMAIN=example.com
MEDUSA_CERT_RESOLVER=letsencrypt
ADMIN_CORS=https://admin.example.com
STORE_CORS=https://store.example.com
JWT_SECRET=
COOKIE_SECRET=
```

The admin and the store urls are not available yet. We are going to deploy them in another platform as it would be explained in another post.

The `MEDUSA_DOMAIN` will be the address of our backend. We could use something like `api.example.com`.

#### Traefik and dashboard
```bash
TRAEFIK_DOMAIN=traefik.example.com
TRAEFIK_CERT_RESOLVER=letsencrypt
TRAEFIK_USER=admin
TRAEFIK_PASSWORD_HASH= # echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g% 
```

![Traefik Dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjdzppcwwrohkqqunhta.png)

We will configure the rest of the variables in a second phase, since we will need to generate the api keys of some of the services.

### Step 3: Start the containers.
The stack is composed of different docker-compose files to deploy each part of the stack. To start the containers, simply run:

```bash
sh start.sh
```

or more explicitly:

```bash
docker compose \
-f docker-compose.medusa.yml \
-f docker-compose.minio.yml \
-f docker-compose.search.yml \
-f docker-compose.traefik.yml \
up -d
```

### Step 4: Access the Medusa stack.
If everything goes well, you could access the different components of your stack:

* Traefik dashboard: https://traefik.example.com
* Medusa server: https://api.example.com
* MeiliSearch: https://search.example.com
* Minio cloud storage: https://minio-console.example.com

You can check also the health of the containers by running:

```bash
turbo@test-medusa:~$ cd medusa-stack-dockerized/
turbo@test-medusa:~/medusa-stack-dockerized$ docker compose ps
NAME                                    COMMAND                  SERVICE             STATUS              PORTS
medusa-server-default                   "./develop.sh"           backend             running (healthy)   0.0.0.0:9000->9000/tcp, :::9000->9000/tcp
medusa-stack-dockerized-meilisearch-1   "tini -- /bin/sh -c …"   meilisearch         running             0.0.0.0:7700->7700/tcp, :::7700->7700/tcp
medusa-stack-dockerized-postgres-1      "docker-entrypoint.s…"   postgres            running (healthy)   
medusa-stack-dockerized-redis-1         "docker-entrypoint.s…"   redis               running             
service-storage                         "/usr/bin/docker-ent…"   storage             running (healthy)   9000/tcp
traefik                                 "/entrypoint.sh --pr…"   traefik             running             0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, :::80->80/tcp, :::443->443/tcp
```

If any of the services is unhealthy you can debug it running:

```bash
docker-compose logs -f <service-name>
In the above command, replace <service-name> with the name of the service you want to debug.
```

**Example:**
```bash
turbo@test-medusa:~/medusa-stack-dockerized$ docker compose logs traefik
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Configuration loaded from flags."
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Traefik version 2.9.8 built on 2023-02-15T15:23:25Z"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Starting provider aggregator aggregator.ProviderAggregator"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Starting provider *traefik.Provider"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Starting provider *docker.Provider"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Starting provider *acme.ChallengeTLSALPN"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Starting provider *acme.Provider"
traefik  | time="2023-03-02T22:47:26Z" level=info msg="Testing certificate renew..." providerName=letsencrypt.acme ACME CA="https://acme-v02.api.letsencrypt.org/directory"
traefik  | time="2023-03-03T22:47:26Z" level=info msg="Testing certificate renew..." providerName=letsencrypt.acme ACME CA="https://acme-v02.api.letsencrypt.org/directory"
traefik  | time="2023-03-04T12:20:26Z" level=error msg="Error while Peeking first byte: read tcp 172.18.0.4:80->151.235.195.76:33779: read: connection timed out"
traefik  | time="2023-03-04T22:47:26Z" level=info msg="Testing certificate renew..." providerName=letsencrypt.acme ACME CA="https://acme-v02.api.letsencrypt.org/directory"
```


### Configure Minio cloud storage
1. Access your Minio console and create a new bucket.
2. Set the policy of the bucket to "Public".
3. Go to "Access keys" and generate the access key and the secret key.
4. Set the variables in `.env` accordingly.

```bash
MINIO_ENDPOINT=https://minio.example.com
MINIO_BUCKET=medusa-test
MINIO_ACCESS_KEY=supers3cr3t
MINIO_SECRET_KEY=v3rystr0ngpassw0rd
MINIO_ROOT_USER=admin
MINIO_ROOT_PASS=changeme1234
MINIO_DOMAIN=minio.example.com
MINIO_CONSOLE_DOMAIN=minio-console.example.com
MINIO_CERT_RESOLVER=letsencrypt
 ```

### Configure MeiliSearch variables

```bash
MEILI_DOMAIN=search.example.com
MEILI_CERT_RESOLVER=letsencrypt
MEILISEARCH_HOST=https://search.example.com
MEILISEARCH_API_KEY=
MEILI_MASTER_KEY=
```

[Secure your MeiliSearch instance.](https://docs.meilisearch.com/learn/security/master_api_keys.html#protecting-a-meilisearch-instance)

Once you set the master key and restart the container your request will be protected. Check the endpoint [https://search.example.com/keys](https://search.example.com/keys), it should show a message informing you the request is not authorized.

![MeiliSearch not authorized request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ievu9puznhjwfji10j2.png)


## Troubleshooting

P: Docker containers don't finish getting up
S: Check that your VPS has at least 1GB of RAM available.

P: Errors requesting the SSL LetsEncrypt certificates.
S: Don't use free TLDs like .ga, .ml, .tk, etc. Ensure you wait enough time to propagate the DNS changes.

P: Can't login to Traefik dashboard.
S: Ensure you generate a correct[ user and hashed password](https://doc.traefik.io/traefik/middlewares/http/basicauth/).

I hope you found my post useful! If you did, please consider giving it [a star on Github](https://github.com/beakman/medusa-stack-dockerized). It will help others find and benefit from the post as well.

Thank you for your support!

[https://github.com/beakman/medusa-stack-dockerized](https://github.com/beakman/medusa-stack-dockerized).