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.
Make sure you did next things before moving forward:
APTIBLE_USERNAME
and APTIBLE_PASSWORD
variables accordinglyCreate App
button). The same as DigitialOcean, Aptible doesn’t automatically create an app on the first run.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:
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.
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:
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:
docker build ...
.git
foldergit tag $(git rev-parse --short HEAD)
tags current commit with commit hashgradle build
gradle-git-plugin fetches last tag and makes it a project versiondocker 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_PROCESS_INDEX
metadata variable as --build-arg
to docker build command.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.
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.