Yury Kirienko
Yury Kirienko

Reputation: 2019

How to deploy a multi-container app to Azure with a Github action?

I have a multi-container app with source code stored on Github. Essentially, there's only one part in active development, and the other containers are either stable (like Nginx with special settings) or external (like redis).

My question is: how can I use Github Actions for the deployment to Azure App Service?

It's rather well-described for a single-container App, and I'm already able to push my image to the Container Registry with an Action. But then I still have to go to Azure web interface and trigger docker-compose from there. Or alternatively, I can trigger docker-compose from the Azure CLI from my local machine.

But the actual problem is to trigger docker-compose from the Github Action (in order to deploy every time my PR into master is validated).

Any ideas?

As a reference point: my docker-compose.yml is like this:

version: '3'

services:
  nginx:
    image: mycr.azurecr.io/nginx:dev-latest
    ports:
      - "80:80"
      - "2222:2222"
    volumes: 
      - asset-volume/app/static
    depends_on:
      - app
    restart: always
  app:
    image: mycr.azurecr.io/django:dev-latest
    ports:
      - "8000:8000"
    volumes: 
      - asset-volume:/app/static
      - app-volume:/app
      - api-documents:/app/documents/storage
  redis:
    image: redis:alpine
  celery:
    restart: always
    command: celery -A mainApp worker -l info
    image: mycr.azurecr.io/django:dev-latest
    volumes:
      - app-volume:/app
    working_dir: /app
    depends_on:
      - app
      - nginx
      - redis

volumes: 
  asset-volume
  app-volume
    

Upvotes: 5

Views: 3006

Answers (1)

Yury Kirienko
Yury Kirienko

Reputation: 2019

The real solution seems to be possible only via Azure CLI, but for the moment I came up with a partial one. That's why I can't mark my own answer as an "accepted solution".

In my action (see below):

  1. I login to Azure and to Docker (all credentials are stored in GitHub Secrets)
  2. I build my App from a Dockerfile
  3. Finally, I push the image to the Azure Container Registry with two tags: latest and <date>-<hash>

Then, on Azure: MyApp on AppService > Deployment Center:

  • Source: Container Registry
  • Container Type: Docker Compose
  • Config: the content of the docker-compose.yml

And that's it. After that, on every merge validation, the GitHub CI pushes a ready-to-go image to a Container Registry. And I only need to hit the "Restart" button on the GUI to restart my app. Then the new latest image will be loaded by Docker. Of course, it's still a manual action, but it's better than nothing.

My delpoyment.yml with the action looks like this (I omit non-essential details):

name: 'Deployment to Azure'
on:
  push:

env:
  DEV_URL: 'my-dev-app.example.com'
  DEV_DBNAME: 'app-dev-db'
  PROD_URL: ''my-app.example.com''
  PROD_DBNAME: 'app-dev-db'


jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: 'Get the branch name'
      id: branch-name
      uses: tj-actions/branch-names@v5

    - name: 'Set RAW_BRANCH variable'
      run: echo "RAW_BRANCH=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV

    - name: 'Checkout repo'
      uses: actions/checkout@v2

    - name: 'Sets branch-related variables'
      # `main` -> `prod`, `dev` -> `dev`,
      # everything else -> `feat`:
      run: |
        if [[ $RAW_BRANCH == 'main' ]]; then
          echo "BRANCH=prod" >> $GITHUB_ENV
        elif [[ $RAW_BRANCH == 'dev' ]]; then
          echo "BRANCH=$RAW_BRANCH" >> $GITHUB_ENV
          echo "DBNAME=${{ env.DEV_DBNAME }}" >> $GITHUB_ENV
          echo "URL=${{ env.DEV_URL }}" >> $GITHUB_ENV
        else
          echo "BRANCH=feat" >> $GITHUB_ENV; fi
          echo "DBNAME=${{ env.DEV_DBNAME }}" >> $GITHUB_ENV
          echo "URL=${{ env.DEV_URL }}" >> $GITHUB_ENV

    - name: 'Set SHA variable'
      run: echo "SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV

    - name: 'Set TAG variable'
      run: echo "TAG=${{ env.BRANCH }}-$(date "+%Y.%m.%d")-${{ env.SHA }}" >> $GITHUB_ENV

    - name: 'Login via Azure CLI'
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}
    - name: 'Login to Container Registry'
      uses: azure/docker-login@v1
      with:
        login-server: ${{ secrets.DOCKER_REGISTRY_SERVER_URL }}
        username: ${{ secrets.REGISTRY_USERNAME }}
        password: ${{ secrets.REGISTRY_PASSWORD }}

    - name: 'build and push'
      run: |
        docker build -t ${{ secrets.DOCKER_REGISTRY_SERVER_URL }}/backend:${{ env.TAG }}  \
                     -t ${{ secrets.DOCKER_REGISTRY_SERVER_URL }}/backend:${{ env.BRANCH }}-latest .
        docker push --all-tags ${{ secrets.DOCKER_REGISTRY_SERVER_URL }}/backend

And then my Config for, say, dev instance in the Deployment Center looks like:

version: '3'

services:
  nginx:
    image: myacr.azurecr.io/nginx:latest
    ports:
      - "80:80"
      - "2222:2222"
    volumes: 
      - asset-volume-dev:/app/static
    depends_on:
      - app
    restart: always
  app:
    image: myacr.azurecr.io/backend:dev-latest
    ports:
      - "8000:8000"
    volumes: 
      - asset-volume-dev:/app/static
      - app-volume-dev:/app
  redis:
    image: redis:alpine
  celery:
    restart: always
    command: celery -A superDuperApp worker -l info
    image: myapp.azurecr.io/backend:dev-latest
    volumes:
      - app-volume-dev:/app
    working_dir: /app
    depends_on:
      - app
      - nginx
      - redis

volumes:
  asset-volume-dev:
  app-volume-dev:

⚠️ NB: For some unclear reason, if I leave the docker-compose.yml in the root directory of the app, then the deployment script will be some weird combination of both that yml and what it written in the Config in Azure GUI. Therefore I had to remove that docker-compose.yml from the root folder of the repo.

Upvotes: 2

Related Questions