---
title: CI/CD configuration
slug: hosty/cicd-configuration
docTags: 
createdAt: 2024-07-05T14:13:54.557Z
---

The approach recommended by Hosty to deploy the code and manage environemnts is through CI/CD platforms like CircleCI, TravisCI, Github Actions and similar.

Hosty has a special [user role](https://wiki.systemseed.com/hosty/roles-and-permissions) called **Release manager** who has permissions to create, update and delete environments on the hosting. It was designed specifically for use in CI/CD platforms.

The recommended deployment workflow consists of several subsequent steps:

- Checkout the git branch and install project dependencies
- Install and configure Hosty CLI
- Install VPN client and connect to the cluster's VPN
- Build and deploy the code
- Run post-deploy scripts
- Disconnect from VPN

### Example

This is simplified example is made using syntax for CircleCI platform, however, similar steps can be applied to any other CI/CD platform.

In this example CI uses `compose.yaml` file in a project root, which will be used by Docker Compose to perform operations via Hosty CLI:

```yaml
services:
  hosty_cli:
    # We highly encourage you to use a stable tag
    # instead of the "latest". List of tags can be found at
    # https://hub.docker.com/r/systemseed/hosty/tags 
    image: systemseed/hosty:latest
    volumes:
      # Required: the root folder of the project must be mapped to
      # the /src/project inside the Hosty container.
      # It is used to build images, track changes, etc.
      # Below is an example mapping assumed that compose.yml file
      # is in the project root (where .git folder is).
      - ./:/src/project

      # In order to run Docker commands inside of the Hosty
      # container we need to have access to the Docker socket of
      # the host machine.
      # The location of Docker socket can be different, depending on
      # your OS.
      - /var/run/docker.sock:/var/run/docker.sock

      # Mapping the folder with AWS credentials received from 
      # the hosting administrators. Note that the folder does not
      # have to be relative to your home folder and can be located
      # in any other place, as long as it contains credentials file
      # with the correct AWS configuration for the hosty.user
      # profile.
      - ~/.aws:/src/.aws
```

In order to store AWS access key, secret key and VPN configuration profile we use environmental variables. Access keys of a dedicated user with Release manager role are stored in `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` variables. The VPN configuration profile file is encoded using base64 (`base64 -i *.ovpn -o encoded.txt`)  and stored in `AWS_VPN_CLIENT_CONFIG` variable.

Here are the contents of `.circleci/config.yml` file:

```yaml
version: 2.1

jobs:

  build_and_deploy:
    machine:
      image: ubuntu-2204:2024.05.1

    steps:
      - checkout

      # Install OpenVPN.
      - run:
          name: Install OpenVPN client required to connect to the hosting
          command: |
            sudo apt-get update
            sudo apt-get install openvpn
      
      # Use base64 to decode VPN configuration profile
      # and connect to VPN.
      - run:
          name: Open VPN connection
          background: true
          command: |
            echo $AWS_VPN_CLIENT_CONFIG | base64 --decode > /tmp/config.ovpn
            sudo openvpn --config /tmp/config.ovpn > /tmp/openvpn.log

      # Make sure VPN connection is established.
      - run:
          name: Wait for VPN connection to establish
          command: |
            counter=1
            until [ -f /tmp/openvpn.log ] && [ "$(grep -c "Initialization Sequence Completed" /tmp/openvpn.log)" != 0 ] || [ "$counter" -ge 5 ]; do
              ((counter++))
              echo "Attempting to connect to VPN server..."
              sleep 1;
            done
            if [ ! -f /tmp/openvpn.log ] || (! grep -iq "Initialization Sequence Completed" /tmp/openvpn.log); then
              printf "\nUnable to establish connection within the allocated time ---> Giving up.\n"
              exit 1;
            else
              printf "\nVPN connected\n"
            fi

      # Pull Hosty image from Docker Hub.
      - run:
          name: Pull Docker Image with Hosty CLI
          command: docker compose pull hosty_cli

      # Configure AWS access keys.
      - run:
          name: Set AWS Access & Secret keys
          command: |
            docker compose run --rm hosty_cli aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID --profile hosty.user
            docker compose run --rm hosty_cli aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY --profile hosty.user

      # Determine environment name based on the branch name, where
      # `main` branch is production and for other branches use git branch name as is.
      - run:
          name: Determine the correct environment name for Hosty deployments
          command: |
            [[ $CIRCLE_BRANCH = "main" ]] && ENV_NAME="production" || ENV_NAME="$CIRCLE_BRANCH"
            echo "export ENV_NAME='$ENV_NAME'" >> $BASH_ENV

      - run:
          name: Install external dependencies, build assets & run code quality checks
          command: |
            echo "Checking if AWS already contains Docker image with the current build..."
            RESULT=$(docker compose run --rm hosty hosty build --exists $ENV_NAME)
            if [ $RESULT = "EXISTS" ]; then
              echo "Docker images for the current build already exist. No need to proceed with building assets."
            else
              echo "Docker images for the current build do not exist. Starting to build assets."
              
              # Here it a perfect place to download app dependencies
              # and build app assets if build doesn't exist.
              # i.e composer install, npm install and npm run build 
              
              # It is important to delete the files not required by
              # application to run. For example as "node_modules"
              # folder, for building assets.
            fi

      # Build Docker image(s) for the environment.
      - run:
          name: Build & push project Docker image(s) to the hosting
          command: docker compose run --rm hosty_cli hosty build --push $ENV_NAME -v

      # Create or update environment on the hosting.
      - run:
          name: Deploy the environment to the hosting
          no_output_timeout: 1800
          command: docker compose run --rm hosty_cli hosty deploy $ENV_NAME -v

      # Perform post-deployment commands for the deployed environment.
      - run:
          name: Execute post deploy commands
          command: docker compose run --rm hosty_cli hosty exec $ENV_NAME --command "ls -la"

      # Get environment URL.
      - run:
          name: Add URL of the deployed environment to the local environment variable
          command: docker compose run --rm hosty_cli hosty domains $ENV_NAME | sed 's/[^ ]* */https://&/g' | awk '{print "BACKEND_URL="$1}' >> $BASH_ENV

      # Display success message.
      - run:
          name: Display deployed message
          command: echo "Deployment completed ($CIRCLE_BRANCH). URL: $BACKEND_URL"

      # Disconnect from VPN.
      - run:
          name: Disconnect from VPN
          command: sudo killall openvpn || true
          when: always

      # Store logs in case VPN connection failed.
      - store_artifacts:
          path: /tmp/openvpn.log
          when: on_fail

workflows:
  version: 2

  deploy:
    jobs:
      - build_and_deploy
```

