I recently created a Slack app, and was thinking about how to publish it. I wanted to test out some changes, but I didn't want to affect the live app. That was when I realized I had to set up dev and prod environments.

My app was being hosted on Google App Engine, so I first had to create a separate service to direct traffic from the test version to. Each service has its own app.yaml config, so you simply add service: dev to your app.dev.yaml or whatever you want to call it, then deploy your project with the service's yaml file, which will create the service. The service will be at the endpoint https://[SERVICE_ID]-dot-[MY_PROJECT_ID].appspot.com; see Google's documentation on routing for more details. Note that things like cron jobs apply to all services.

Structure-wise, Google's documentation says that you should have separate subfolders for each service. However, I didn't want to do that as I wanted the changes to be differentiated by version control branches, rather than having duplicate files. Fortunately, each service works independently of each other, so you just need to deploy the correct yaml file. Hence, I kept everything in the root directory.

I also created a separate test app on api.slack.com and configured it to use the dev endpoints. This generates a separate set of credentials, obviously. If you're using slash commands, I recommend choosing something like /test-myapp rather than /myapp-test as people using your app are more likely to use the latter by mistake when they type /myapp.

The most complicated thing was configuring CircleCi. Previously, what I was doing was storing credentials as CircleCi environment variables, generating app.yaml in a build job using bash commands, and adding the variables in. I added the test credentials and started creating 2 yaml files, app.dev.yaml and app.prod.yaml. Then I cached them separately, so that later jobs could use the appropriate one based on the environment: I had a separate, common cache for things like requirements (I suppose this is not ideal if I want to test out new libraries, but I don't foresee that happening much).

  - save_cache:
      paths:
        - app.prod.yaml
      key: v1-config-prod

  - save_cache:
      paths:
        - app.dev.yaml
      key: v1-config-dev

I then duplicated my test and deploy jobs to run for commits to dev and master separately, using the respective yaml file. For example:

  - restore_cache:
      keys:
      - v1-config-dev

...

  - run:
      name: deploy
      command: |
        gcloud auth activate-service-account --key-file=${HOME}/key.json
        gcloud --quiet config set project $GOOGLE_PROJECT_ID
        gcloud app deploy ./app.dev.yaml ./cron.yaml

The workflow appears as such:

workflows:
  version: 2
  build-and-test:
    jobs:
      - build
      - test:
          requires:
            - build
          filters:
            branches:
              only: master
      - test-dev:
          requires:
            - build
          filters:
            branches:
              only: dev
      - deploy:
          requires:
            - test
          filters:
            branches:
              only: master
      - deploy_dev:
          requires:
            - test-dev
          filters:
            branches:
              only: dev

So the end result is that whenever a commit is pushed to dev or master, the appropriate workflow is triggered and deployment goes to the respective service.