Kelvin Tay

enjoys technical writing, and a cheeky drink 🥃

I have built a quick tool (in Python) that calculates the time taken for agents to follow up on a customer ticket on Zendesk.

If this is also useful for your support team, feel free to copy from the source code here.


As a senior support engineer, I follow up with customers who left a negative review on their support experience.

We noted that some dissatisfied customers felt that they waited too long for a response. However, this is usually due to genuine misunderstandings and expectations around the response time SLAs for their support plan.


To make it easy for customers and ourselves to confirm our response times within a ticket, I built this tool that:

  • calculates the time taken for the first agent response to each comment from the customer
  • provides the arithmetic for proof
  • includes information of when weekends were involved

In this tool, I elected to keep the calculation of response times agnostic to the specific plan of the customer; It merely calculates the time taken between a customer comment and an agent's response.

I find this useful, since Support plans (and their promised SLAs) may change overtime. Keeping the tool really simple allows anyone to build on top of the output (CSV file), for flexibility.

Please feel free to copy as needed. 🍺

buy Kelvin a cup of coffee


Before Diving in

This is an attempt to explain and explore how teams can use Docker Buildx for delivering Docker images.

Since we will not be covering all features around Docker Buildx, this is a wide snorkel rather than a deep dive.

This is a quick article for developers who have yet to use Docker Buildx but are curious on its use-cases.

What is Docker Buildx?

Let's take a few steps back before plunging in.

We use Docker Build to build Docker images from Dockerfiles.

Since 18.09, BuildKit was introduced as an improved version of the previous builder. As an example, we can mount secrets when building our images with BuildKit. BuildKit will also ensure that these secrets are not exposed within the built image's layers.

Buildx builds (no pun intended) on top of BuildKit. It comes with more operations besides image-building, as you can see from its available commands. Importantly, Buildx provides features for caching and cross-platform image builds.

Why should we use Docker Buildx?

For software teams shipping Docker images often, Docker Buildx can be an important tool in the box.

Caching image layers ensure a next rebuild of the image will be faster.

Before, teams would need various machines on different platforms to build images for each platform. For example, we would need a ARM64 machine to build a Docker image for ARM64 architectures.

With Docker Buildx's cross-platform feature, we can now use the same AMD64 machine to build both AMD64 and ARM64 Docker images.

Why is it relevant in CI/CD?

Many teams are building Docker images as part of their CI/CD pipelines. Hence, they can lean on the build cache and cross-platform capabilities of Docker Buildx to build various images faster and cheaper.

Let's discuss the two mentioned features a little deeper.


This pertains to the cache-from and cache-to options with the docker buildx build command.

Docker Buildx allows you to choose your caching strategy (e.g., inline, local, registry and etc), and each comes with its pros and cons.

Your choice will depend largely on your team's philosophy and the CI/CD provider.

For example, you can leverage GitHub's Cache service when running Docker Buildx on GitHub Actions.

For CircleCI users, you may find my exploratory project here useful.


When building an ARM64 Docker image on a CI/CD pipeline, you would need to do so on an ARM64-based machine runner then (if not using Buildx).

Depending on your CI/CD provider, there may not be ARM64 support.

This can be worked around, if your CI/CD provider allows you to “bring you own runners” (also known as self-hosted runners). GitHub Actions and CircleCI support self-hosted runners. However, it does mean someone in your team now has to manage these runners on your infrastructure.

With Docker Buildx, we can now build cross-platform images within any arbitrary machine runner.

This can be a big win for team that prefers not owning additional infrastructures.

Resurfacing to Shore

We have explored the appeal of Docker Buildx, particularly in a CI/CD context here. As mentioned, it is ultimately a tool. For teams building Docker images in their CI/CD pipelines, I do encourage you to look into Docker Buildx if you have not!

#docker #buildx #cicd #performance

buy Kelvin a cup of coffee


It has been an eventful year so far being a part of CircleCI's Support Engineering team. This also marks my first year in Customer Engineering.

This has been an interesting challenge for me, as I transitioned from a past life as a software engineer.

I have learned a lot. I hope this list will be useful for folks switching to Customer Engineering like myself.

1. Empathy is important

Empathy is an important element in communication. This remains true even when two engineers work through a problem.

Customers write in a support ticket because they need help solving an issue. They likely spent a lot of time trying to solve it, and are frustrated at this point. On that note, a good error message will help users better than a technical support answer can.

In addition, English may not be the main or strongest language of the customer. I find that short and simple instructions can go a long way.

Always remember to factor these points, when you reply.

2. Detect XY Problems early

It helps to see the bigger picture of a customer's ask. At times, their original question can be a XY problem.

I find that these questions tend to exhibit the following smells:

  • the customer is trying to do something that veers from the common use-case or best practices
  • the solution they are looking for, and their end goal are disconnected.

In these situations, never hesitate to confirm what they are trying to solve exactly. This makes sure that you understand their problem and end goal.

This brings about my next point below.

3. Clarify, clarify and clarify

Solving a problem requires understanding the problem first.

Not everyone is great at writing about their problem at hand. There will likely be details missing, and these could be important.

Remember that asking further questions is also a valid response. It is even better when you can explain how their answers will help in the next step.

For example,

“Would you be able to do 123, and share the output here? This will help us confirm or rule out ABC.”

At times, these questions can help customers arrive at their own answer or solution.

4. Anticipate the next question

Solving an immediate problem for the customer feels great! Anticipating potential challenges, and sharing them is even better.

I find this to be a useful trick when dealing with new users in particular.

New users are likely still trying to figure out how to use your product. Beyond the immediate question, they may bump into another hurdle soon.

If you can anticipate this early, share your knowledge while you close out the ticket. This proactiveness will help with ticket deflection too.

5. Share knowledge

Overtime, you may end up being the “subject matter expert” for certain topics around the product.

Instead of being the bottleneck for information, make efforts to distill this knowledge to the rest of the team.

This can come in the form of:

  • pair ticketing (like pair programming!)
  • internal or public knowledge base (even better)
  • Ask Me Anything (AMA) sessions

This can only help the team, as there becomes a better balance of knowledge amongst members.

You can apply for time off in peace, knowing that your teammates got this!

Oh, and one last piece of advice. Slack should never be your team's knowledge base; Invest in documentation! 📖

Useful articles

#customerengineering #empathy #software

buy Kelvin a cup of coffee

This is an attempt to explain CircleCI's Docker Layer Cache (DLC) feature in simple terms.

What is Docker Layer Cache

Docker creates an image via layers. Each layer is created via Dockerfile commands like RUN, COPY and ADD, and they represent a unit of filesystem change.

When Docker builds an image, the built layers are stored. Rebuilding the same image becomes faster because Docker can now retrieve the stored layers as cache. This is known as layer caching on Docker, or Docker Layer Cache (DLC).

DLC is useful when we are rebuilding the same layers. It works well when we build images on the same machine; The stored layers are still there when we build a second time.

However, this becomes tricky on CI/CD systems, since most CI/CD pipelines run on ephemeral environments. The execution environment is destroyed once your pipeline completes.

How does CircleCI implement DLC

For DLC to work on CI/CD systems, we need to ensure stored/cached layers can be shared between CI/CD pipelines.

To achieve this, CircleCI assigns an external volume (store) to a job when we enable DLC. Think of it like passing around an external hard drive amongst friends.

This external volume will store the built Docker images' layers in a job. When CircleCI assigns same external volume to the next job, we should see faster image builds since the cached layers can be found.

An external volume can be assigned only to one job at a time. Hence, a job may see cache misses when building images due to a different volume being assigned.

A cache miss will mean a longer time taken to build the Docker image, which translates to a longer job duration. However, it does not and should not cause a job on CircleCI to fail.

#docker #dockerlayercache #circleci #explanation

buy Kelvin a cup of coffee

Enter your email to subscribe to updates.