How to use gitlab-ci.yml to execute tests prior to Render deployment?

Deployment setup: Render connected with GitLab for a Phoenix.js app
CI system: Gitlab.com using gitlab-ci.yml with a test stage

Problem: Currently, when I push to Gtlab (main) both my CI tests and CD deployment to Render.com are conducted in parallel
Desired state: Ideally, I’d like to have this run in serial. First CI, and only on CI success, then deploy to Render.

Anyone know what I should do to achieve the bliss of the desired state?

Good question, @Jesse_Blum! I’d recommend you use the deploy hook for your Render service. To make this work, you’d first want to turn off Auto Deploy from your service’s settings page. Then make a request to the deploy hook in your CI pipeline, conditional on tests passing.

1 Like

Hi @david,

Thanks for the pointers, they were really helpful. For anyone interested, below are the exact steps I used for a complete solution that masks the deploy hook:

  1. Turn off Auto Deploy from Render web service settings page

  2. Copy Deploy Hook from the Render web service settings

  3. Since this is a private URL to trigger a deploy for this server, it should be kept secret.

  • Gitlab CI/CD has a mechanism to keep variables safe called “Mask variable”. Masking the variable ensures that the real Deploy Hook value is not leaked into the CI/CD logs. But to mask requires splitting up the Deploy Hook URL, because of restrictions on the character set that can be masked. Here is how I split it up, which I tested from my local development environment using local environment variables and curl:

    DEPLOY_HOOK_01=[SECRET_INFORMATION_01]
    DEPLOY_HOOK_02=[SECRET_INFORMATION_02]
    curl -s “https://api.render.com/deploy/srv-{DEPLOY_HOOK_01}? key={DEPLOY_HOOK_02}” -o file.txt >/dev/null 2>&1

  • Once split, add new variables called “DEPLOY_HOOK_01” and “DEPLOY_HOOK_02” to the Gitlab project’s Settings > CI/CD > Variables. Set the values of the environment variable to the appropriate parts of the Deploy Hook copied in step 1. The appropriate parts are only [SECRET_INFORMATION_01] and [SECRET_INFORMATION_02] – don’t bother with “https://” and the rest.

  • Unfortunately, the response from the API call returns some of the information we’re trying to keep secret. Therefore, the use of the Curl “-s” silent parameter and piping the response to the “black hole” (-o file.txt >/dev/null 2>&1) is an additional precaution used to avoid secret information being logged. The response to the Deploy Hook API call is in the form: {“deploy”:{“id”:“dep-[SECRET_INFORMATION_01]”}}.

– Aside to Render engineers: Perhaps alter the API so as not to return the secret. It would still be nice to log some kind of success response.

  1. Add a deployment block in gitlab-ci.yml (conditional on tests passing). Below is a gitlab-ci.yml for a Phoenix.js app that automatically runs tests and then deploys to production on Render.

  2. It’s also a good practice to add an environment sub-section of the the deployment section. That allows Gitlab to add an Operations > Environment. Adding a link to the dashboard.render.com service makes it easy to navigate to from within the Gitlab production environment (the link is added to a button labeled “View Deployment” within Operations > Environments > production").

  3. Testing this is really easy. Once the main branch has a committed update to the .gitlab-ci.yml, the new CI/CD pipeline is triggered. Once tests pass the deploy_production job is triggered. If successful, then the Render web service will list the latest deploy.

For anyone curious, CI testing of a vanilla Phoenix.js app takes about 3.5 minutes to complete, and CD costs roughly similar – so around 7minutes total for CI/CD.

.gitlab-ci.yml

stages:
  - test
  - deploy
image: bitwalker/alpine-elixir-phoenix:latest
test:
  stage: test
  services:
    - postgres:latest
  variables:
    POSTGRES_HOST: postgres
    POSTGRES_USER: postgres # must match config/test.exs
    POSTGRES_PASSWORD: postgres # must match config/test.exs
    MIX_ENV: "test"
  script:
    - mix deps.get --only test
    - mix ecto.create
    - mix ecto.migrate
    - mix test
  only:
    - main
    - merge_requests
deploy_production:
  stage: deploy
  script:
    - echo "Deploy to production server"
    - curl -s "https://api.render.com/deploy/srv-${DEPLOY_HOOK_01}?key=${DEPLOY_HOOK_02}" -o file.txt >/dev/null 2>&1
  environment:
    name: production
    url: https://dashboard.render.com/web/srv-${DEPLOY_HOOK_01}
  only:
    - main
2 Likes

@Jesse_Blum Amazing!! Thanks for taking the time to write up your solution. This is quality stuff.

FYI only the deploy hook key (what you’re calling DEPLOY_HOOK_02) needs to be treated as a secret. DEPLOY_HOOK_01 is a unique identifier for your service, and the value in the deploy hook response object is a unique identifier for the deploy. There’s nothing a bad actor could do with these identifiers unless they’re also able to log into your account (at which point they can find the identifiers anyway). That said, I can understand the instinct to keep them private.

1 Like