Preview deployments with Serverless Framework on GitLab
Tags: serverless, tutorial, gitlab, devops
Published: 2022-01-08 11:57:05

If you are using [Serverless Framework](https://www.serverless.com/), it already has a built-in preview deployment feature with [Serverless CI](https://www.serverless.com/ci-cd). You can read [_A Guide to Preview Deployments with Serverless CI/CD_](https://www.serverless.com/blog/preview-deployments) for more information. 


![Serverless Dashboard - CI/CD settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uptsuue92zlzrhasjdck.png)

[Serverless CI](https://www.serverless.com/ci-cd) supports [GitHub](https://www.github.com) and [Bitbucket](https://bitbucket.org/) out of the box and you can manage it from [Serverless Dashboard](https://www.serverless.com/dashboard/) by going to your project's CI/CD settings.

In this post, you will be reading about how to implement preview deployments by using [GitLab CI/CD](https://docs.gitlab.com/ee/ci/). The setup may change depending on your requirements so this will be just a simple example regarding how to achieve this integration. I will assume that you have a basic understanding of [GitLab's CI Jobs](https://docs.gitlab.com/ee/ci/jobs/).

## serverless.yml initial setup

We will be using custom domain manager plugin `serverless-domain-manager` to build our preview domains as well as staging and production domains. 

`yarn add serverless-domain-manager` or `npm i serverless-domain-manager`

```yaml
# serverless.yml
org: myorg
service: my-service
app: my-service

plugins:
  - serverless-domain-manager

provider:
  name: aws
  stage: ${opt:stage, 'local'}
  region: ${opt:region, 'eu-central-1'}
  endpointType: REGIONAL
  environment:
    STAGE: ${sls:stage, 'local'}
```

## Adding custom domain settings

We will have four targets for the type of domain names. `production`, `staging`, `preview` and `local`. Each preview deployment will be a different stage and you will be seeing them like this on Serverless Dashboard:

![Preview deployments as stages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p8jivj400lqnwkz7j6u6.png)

The domains will be created in these formats regarding their stages:

Domain target: `production`. 
Stage: `production`. 
Domain: `production-api.myapp.org`.

Domain target: `staging`. 
Stage `staging`. 
Domain: `staging-api.myapp.org`

Domain target: `preview`. 
Stage `preview-1`. 
Domain `preview-1-api.myapp.org`. 
(`1` is your merge request ID coming from [GitLab's predefined variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html#predefined-variables-for-merge-request-pipelines))

**Addition:**

```yaml
custom:
  domains:
    production: production-api.myapp.org
    staging: staging-api.myapp.org
    preview: "preview-${env:CI_MERGE_REQUEST_IID, ''}-api.myapp.org"
    local: localhost
  customDomain:
    domainName: ${self:custom.domains.${env:DOMAIN_TARGET_TYPE, 'local'}}
    basePath: ""
    stage: ${sls:stage, 'local'}
    createRoute53Record: true
```

If you would like to have multi-regions, you can expand `customDomain` with these options:

```yaml
  customDomain:
    # ...
    endpointType: "regional"
    certificateRegion: ${opt:region, 'eu-central-1'}
    route53Params:
    routingPolicy: latency
```

## .gitlab-ci setup

Now we have our `serverless.yml` ready (assuming that you have completed the rest of settings for your needs such as functions, etc.), we can start building our GitLab CI/CD file. 

We will create a `.gitlab-ci` file and add a `deploy` stage. When a merge request is created, we will automatically deploy our preview deployment `eu_preview_deployment` with two main commands:

`DOMAIN_TARGET_TYPE=preview serverless create_domain --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1`

and

`DOMAIN_TARGET_TYPE=preview serverless deploy --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1`

Domain target type is retrieved from environment variables as you can see. The first one creates the domain, the second one deploys the application. Here, you can give any name to your stages. It doesn't have to start with `preview`. 

After our merge request is _closed_ or _merged_, we will be calling our job `stop_eu_preview_deployment` which is referred by `eu_preview_deployment` in `environment.on_stop: stop_eu_preview_deployment`. Both of the jobs **must be** in the same environment.

The `stopping` job will call two commands:

`DOMAIN_TARGET_TYPE=preview serverless delete_domain --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1`

and

`DOMAIN_TARGET_TYPE=preview serverless remove --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1`

The first one will remove the custom domain, and the second one will remove the preview deployment.

```yaml
image: node:14-alpine

.install_serverless: &install-serverless
  before_script:
    - yarn global add serverless --prefix /usr/local

stages:
  - deploy

eu_preview_deployment:
  <<: *install-serverless
  stage: deploy
  script:
    - DOMAIN_TARGET_TYPE=preview serverless create_domain --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1
    - DOMAIN_TARGET_TYPE=preview serverless deploy --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1
  environment:
    on_stop: stop_eu_preview_deployment
    name: development
    url: https://preview-${CI_MERGE_REQUEST_IID}-api.myapp.org
  only:
    - merge_requests

stop_eu_preview_deployment:
  <<: *install-serverless
  stage: deploy
  variables:
    GIT_STRATEGY: none
  when: manual
  script:
    - DOMAIN_TARGET_TYPE=preview serverless delete_domain --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1
    - DOMAIN_TARGET_TYPE=preview serverless remove --stage preview-${CI_MERGE_REQUEST_IID} --region eu-central-1
  environment:
    name: development
    action: stop
  only:
    - merge_requests
```
That's it... Now you have a `preview` deployment setup.

As an example, I will add `staging` and `production` deployment setups for `.gitlab-ci.yml`:

```yaml
deploy_to_staging_eu:
  <<: *install-serverless
  only:
    - staging
  stage: deploy
  script:
    - DOMAIN_TARGET_TYPE=staging serverless create_domain --stage staging --region us-east-1
    - DOMAIN_TARGET_TYPE=staging serverless deploy --stage staging --region us-east-1
  environment:
    name: development
    url: https://staging-api.myapp.org
  only:
    - branches
    - merge_requests

deploy_to_production_eu:
  <<: *install-serverless
  only:
    - master
  stage: deploy
  script:
    - DOMAIN_TARGET_TYPE=production serverless create_domain --stage production --region eu-central-1
    - DOMAIN_TARGET_TYPE=production serverless deploy --stage production --region eu-central-1
  environment:
    name: production
    url: https://production-api.myapp.org
```

You can change the rules of the setup regarding your needs. You can read more about `.gitlab-ci.yml` keyword references [here](https://docs.gitlab.com/ee/ci/yaml/). You can also change the stages and everything as you wish. 

I hope this small tutorial will give you an idea regarding preview deployments on GitLab. Let me know if you have a better approach. :)