Seamless Deployment to Aptible: Choosing the Right Approach

Written by vladimirf | Published 2023/09/08
Tech Story Tags: devops | cicd | aptible | spring-boot | git | deploying-your-app-to-aptible | aptible-guide | digitalocean-to-aptible

TLDRExploring ways to deploy simple spring-boot application to Aptiblevia the TL;DR App

Aptible is a No Infrastructure Platform that offers robust solutions for simplifying and enhancing the security and compliance aspects of software applications. Aptible streamlines the complex process of achieving and maintaining industry-standard certifications such as HIPAA, SOC 2, and GDPR.

Sounds interesting, isn’t it? Like many of you I love to try new things. Aptible gives one month of a free trial which is just perfect to poke around.

Let’s assume you have an existing app hosted somewhere (in my case it’s DigitalOcean) and you really want to try out Aptible. I took almost-one-line-spring-boot-app from my previous article about Release Driver Development. There we had a decent yet simple CI/CD pipeline based on Github Action. So let’s go ahead and make it work with Aptible.

Aptible provides a nice way to deploy the code right from Git. We will take a closer look down below.

Prerequisite

Make sure you did next things before moving forward:

  • Add Aptible’s username and password to Github Action’s secrets under APTIBLE_USERNAME and APTIBLE_PASSWORD variables accordingly
  • Make sure to create a new App manually (go to Aptible’s Dashboard → choose your environment → Create App button). The same as DigitialOcean, Aptible doesn’t automatically create an app on the first run.

Changing existing pipeline

To be able to deploy to Aptible we need to add a new step to our existing Github Actions pipeline. I don’t want to get rid of DigitalOcean step, so let’s add a switcher to the env section together with Aptible-related params:

env:
  # Where to deploy. Options are 'digitalocean' or 'aptible'
  DEPLOY_TARGET: 'aptible'
  
  # Aptible deploy params
  APTIBLE_ENVIRONMENT: 'google-aeb'
  APTIBLE_APP: 'deployinator'

For the deployment step we are using Deploy to Aptible Github Action. It is pretty self explanatory, you just need to specify credentials, what, and where you are deploying.

The step looks like:

- name: Deploy to Aptible
  if: ${{ env.DEPLOY_TARGET == 'aptible' }}
  uses: aptible/aptible-deploy-action@v1
  with:
    username: ${{ secrets.APTIBLE_USERNAME }}
    password: ${{ secrets.APTIBLE_PASSWORD }}
    environment: ${{ env.APTIBLE_ENVIRONMENT }}
    app: ${{ env.APTIBLE_APP }}
    docker_img: ${{ secrets.DOCKERHUB_USERNAME }}/deployinator:${{env.RELEASE_VERSION}}

Before the actual deployment, the step pulls the image from Dockerhub and pushes into Aptible’s Docker registry.

Perfect, let’s try it out. Committing the changes, pushing to master and … this is what we see on the Aptible Dashboard:

Troubleshooting (unplanned section)

I know, I promised it to be seamless. It will! We just need to figure out a root cause for the failure. I wasn’t able to find any kind of logs neither on the dashboard nor in the Github’s build - it was green. Nothing I could find by googling the error, official doc says:

If endpoint provisioning fails, restart your app using the aptible restart command. You will see a prompt asking you to do so.

Note this failure is most likely due to an app health check failure. We have troubleshooting instructions here: My deploy failed with HTTP health checks failed

The only way to check what exactly has happened is to install Aptible CLI and restart the app.

Frankly speaking, installing CLI to get logs is not the friendliest way to understand what’s wrong with the deployment. It would be great if Aptible could provide more insights on the dashboard.

> brew install --cask aptible

# be ready to provide login/password
> aptible login 

> aptible restart --app deployinator

Among other output logs we can find next errors:

...
ERROR -- : Your Docker image must EXPOSE a port to provision an Endpoint, add an EXPOSE directive in your Dockerfile.
ERROR -- : FAILED: Wait up to 180s for containers in service cmd to respond to HTTP health checks
...

We have to add EXPOSE 80 to the Dockerfile to let Aptible know how to configure the endpoint and do the heath checks. We also need to change Spring Boot server’s port accordingly by adding server.port=80 to application.properties file. You can take a look at the full code at deploy-aptible branch.

On the second attempt we can see app’s Endpoint automatically created

That’s way better. The only thing our app can do is to show the version under /version endpoint. So, copy the hostname from the endpoint and try it.

Perfect, it works.

Deploy on Git Push

Another option I mentioned in the beginning is to deploy by Git Push. To make it work you have to press the Deploy Code button on the Dashboard and go through a pretty straightforward setup process which includes providing your public ssh key and adding Aptible’s Git repo as a remote for your existing repo. The idea here is by pushing into Aptible’s Git remote you trigger (re-)deployment. It’s completely fine to to work with your existing Git repo (named origin) as before, but when you need a deploy, just run something like:

git push aptible main

Aptible will find Dockerfile in your repo, build an image based on it, and deploy as a container. Pretty cool, huh? As a small additional bonus, you can see deployment logs right in your console.

Seems like we don’t need the Github Actions pipeline anymore. Aptible deploys your app based on what you have in a Git repo. Which is absolutely nice! But it’s only from the first sight. This automated approach works good only for small apps, while having drawbacks working with a bigger one.

Think about:

  • How to manage versions as you no longer have CI/CD pipeline?
  • What if you need additional steps (run integration tests, publish artifacts, etc) before deployment?
  • Dockerfile becomes overloaded by extras like gradlew build because it’s the only thing between you and Aptible.

Let’s try this out and see how far it will bring us. First of all, we have to make Dockerfile self-sufficient by moving everything inside. By ‘everything‘ I literally mean everything that we had in the pipeline. Take a look at the result and I will explain it down below:

# Temp container to run gradle build
FROM gradle:latest AS BUILD
COPY --chown=gradle:gradle . .
USER gradle

# Tag current commit with commit's sha for versioning purposes
RUN git tag $(git rev-parse --short HEAD)
RUN gradle build

# Main stage
FROM eclipse-temurin:17
EXPOSE 80
COPY --from=BUILD /home/gradle/build/libs/deployinator-*.jar /app/deployinator.jar
ENTRYPOINT java -jar /app/deployinator.jar

We are using a multi-stage build. First phase is to build a jar file. To make it happen we start with temporary gradle:latest image, copy all the files inside and run gradle build. There is also one trick in here. As we copied all the source files, we also have .git folder inside the image (it is temporary, no worries about extra files). The reasoning behind this is to let gradle-git-plugin access Git tags and provide version info for Gradle.

So, the overall deployment flow looks like:

  1. You push a branch into Aptible’s Git repo
  2. Aptible starts a deployment by doing a shallow clone of the repo
  3. Aptible finds Dockerfile and kicks off docker build ...
  4. Docker goes over instructions in a Dockerfile and copies everything from a cloned repo inside a container, including a .git folder
  5. Command git tag $(git rev-parse --short HEAD) tags current commit with commit hash
  6. During gradle build gradle-git-plugin fetches last tag and makes it a project version
  7. As build finishes, Aptible triggers docker run ... and exposes our app to the world on a port defined in the EXPOSE statement.

Commit hash is not the best option for a project’s version. SemVer or simply buildNumber + gitHash is slightly better (a bit more info about it in a previous article).

However, I couldn’t find a way to access anything similar to buildNumber.

  • Aptible doesn’t seem to add APTIBLE_PROCESS_INDEX metadata variable as --build-arg to docker build command
  • Configuration variable file .aptible.env is not accessible from the container’s root

If you were able to find a way to get a sequential build number (or any other config var which might be helpful) from within Dockerfile - let me know in the comments.

All the code is available under deploy-aptible-on-git-push branch. Now let’s make it deployed by running:

# Check current hash
> git rev-parse --short HEAD 
703496a

# Kick-off deployment
> git push aptible deploy-aptible-on-git-push:main

Waiting for a little bit and…

Perfect, commit hash is the same. We have a dirty flag here which means that Aptible changed (probably added) something after git clone and introduced an unstashed change.

It’s worth mentioning that Aptible provides additional configs for Releases, you can take a closer look here.

Conclusion

Moving a simple app from DigitalOcean to Aptible having a Github Actions pipeline is a pretty easy. You just have to change the deployment step with almost no changes to the code (except explicitly adding http port if necessary). When you have things clearly separated and independent from each other, interchangeability works perfectly.

Another way could be setting up deployment on Git Push. It's a very straightforward and nice-looking approach. However, I’m still skeptical about the fact that it’s pretty limited and forces you to concentrate CI/CD actions in a Dockerfile. Which is not something that leads you toward a clear separation of concerns.


Written by vladimirf | Software Engineer at Google
Published by HackerNoon on 2023/09/08