Chapter 1: Code, Build, Test, and Repeat – The Application Development Inner Loop
Building and deploying cloud-native applications can be cumbersome for local and remote development if you are not using the appropriate tools. Developers go through a lot of pain to automate the build, push, and deploy steps. In this book, we will introduce you to Skaffold, which helps automate these development workflow steps. You will learn how to use the Skaffold CLI to accelerate the inner development loop and how to create effective continuous integration/continuous deployment (CI/CD) pipelines and perform build and deployment to manage Kubernetes instances such as Google Kubernetes Engine (GKE), Microsoft's Azure Kubernetes Service (AKS), and Amazon's Elastic Kubernetes Service (EKS).
This chapter will define the inner loop for application development and its importance, comparing the inner with the outer development loops, and cover the typical development workflows for a traditional monolith application and a container-native microservices application. We will have an in-depth discussion about the differences between these two approaches.
In this chapter, we're going to cover the following main topics:
- Understanding what the application development inner loop is
- Inner versus outer development loops
- Exploring the traditional application development inner loop
- Checking out the container-native application development inner loop
By the end of this chapter, you will understand the traditional and container-native application inner development loops.
Technical requirements
To follow along with the examples in this chapter, you need the following:
- Eclipse (https://www.eclipse.org/downloads/) or IntelliJ IDEA (https://www.jetbrains.com/idea/download/)
- Git (https://git-scm.com/downloads)
- Spring Boot 2.5 (https://start.spring.io)
- minikube (https://minikube.sigs.k8s.io/docs/) or Docker Desktop for macOS and Windows (https://www.docker.com/products/docker-desktop)
- OpenJDK 16 (https://jdk.java.net/16/)
You can download the code examples for this chapter from the GitHub repository at https://github.com/PacktPublishing/Effortless-Cloud-Native-App-Development-Using-Skaffold/tree/main/Chapter01
Understanding the application development inner loop
The application development inner loop is an iterative process in which a developer changes the code, starts a build, runs the application, and then tests it. If something goes wrong, then we repeat the entire cycle.
So basically, it is the phase before a developer shares the changes done locally with others. Irrespective of your technology stack, the tools used, and personal preferences, the inner loop process may vary, but ideally, it could be summarized into the following three steps:
- Code
- Build
- Test
Here is a quick visual representation of the inner development loop:

Figure 1.1 – Inner loop
If you think about it, coding is the only step that adds value, and the rest of the steps are like a validation of your work, that is, confirming whether your code is compiling and tests are passing or not. Since developers spend most of their time on the inner loop, they don't like spending too much time on any of the steps. It should be swift. Moreover, as developers, we thrive on fast feedback.
All the steps that we have defined until now are happening locally on a developer's machine before committing the code to a source code repository. Once a developer commits and pushes changes to the source code repository, it typically starts their CI/CD pipeline, called the outer development loop (pull request, CI, deployment, and so on). Whether you are developing traditional monolith or container-native microservices applications, you should not neglect the importance of your inner development loop. Here is why you should care about your inner development loop:
- If your inner development loop is slow and lacks automation, then the developer's productivity will plunge.
- It would be best if you always aimed to optimize it because a slow inner loop will affect other dependent teams, and it will take much longer to deliver a new feature to your users.
Now that we've had a quick overview of the application development inner loop, let's compare the inner and outer development loops.
Inner versus outer development loops
As discussed earlier, as long as the developer works in their local environment to test things, they are in the inner loop. In general, a developer spends most of their time in the inner loop because it's fast and gives instant feedback. It usually involves the following steps:
- A developer starts working on a new feature request. Some code changes are done at this point.
- Once the developer feels confident about the changes, a build is started.
- If the build is successful, then the developer runs the unit tests.
- If the test passes, then the developer starts an instance of the application locally.
- They will switch to the browser to verify the changes.
- The developer will then trace logs or attach a debugger.
- If something breaks, then the developer will repeat the preceding steps.
But as soon as a developer commits and pushes the code to a source code repository, it triggers the outer development loop. The outer development loop is closely related to the CI/CD process. It involves steps such as the following:
- CI checking out the source code
- Building the project
- Running functional and integration test suites
- Creating runtime artifacts (JAR, WAR, and so on)
- Deploying to the target environment
- Testing and repeating
All the preceding steps are typically automated and require minimal to no involvement on the part of a developer. When the CI/CD pipeline breaks because of a test failure or compilation issue, the developer should get notified and then start working again on the inner development loop to fix this issue. Here is a visualization of the inner loop versus the outer loop:

Figure 1.2 – Inner loop versus outer loop
It's very tempting to use CI/CD as a replacement for your inner development loop. Let's discuss whether this is a good approach or not.
Why not use CI/CD?
Contrary to what we just discussed about the inner loop, some developers may say that they don't care about their inner development loop because they have a CI/CD process for it, which should suffice. They are not entirely wrong as these pipelines are purpose-built to make the process of modern application development repeatable and straightforward. Still, your CI/CD process only solves a unique set of problems.
Using CI/CD as a replacement for your inner development loop will make the entire process even slower. Imagine having to wait for the whole CI/CD system to run your build and test suite, and then deploy only to find out that you made a small mistake; it would be quite aggravating. Now, you would have to wait and repeat the entire process just because of some silly mistake. It would be much easier if we can avoid unnecessary iterations. For your inner development loop, you must iterate quickly and preview changes as if they are happening on a live cluster.
We have covered enough basics about the application development inner loop, and now we will cover the traditional application development inner loop for Java developers.
Exploring the traditional application development inner loop
Before containers were cool, we were spoilt by the choices we have for the inner development loop. Your IDE can run builds for you in the background, and then you can deploy your application and test your changes locally. A typical traditional application development inner loop involves steps such as the following:
- A developer making code changes in an IDE
- Building and packaging the application
- Deploying and then running locally on a server
- Finally, testing the changes and repeating the step
Here is a visualization of the traditional application development inner loop:

Figure 1.3 – Traditional application development inner loop
For Java developers, there are many options available to automate this process. Some of the most popular options are as follows:
- Spring Boot Developer Tools
- JRebel
Let's discuss these options briefly.
Spring Boot Developer Tools
Spring Boot first introduced developer tools in version 1.3. Spring Boot Developer Tools provide fast feedback loops and automatic restart of the application for any code changes done. It provides the following functionalities:
- It provides a hot reloading feature. As soon as any file changes are done on
classpath
, it will automatically reboot the application. The automatic restart may differ based on your IDE. Please check the official documentation (https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/html/using-boot-devtools.html#using-boot-devtools-restart) for more details on this. - It provides integration with the LiveReload plugin (http://livereload.com) so that it can refresh the browser automatically whenever a resource is changed. Internally, Spring Boot will start an embedded LiveReload server, which will trigger a browser refresh whenever a resource is changed. The plugin is available for most popular browsers, such as Chrome, Firefox, and Safari.
- It not only supports the local development process, but you can opt-in for updating and restarting your application running remotely on a server or cloud. You can enable remote debugging as well if you like. However, there is a security risk involved in using this feature in production.
The following is a short snippet of how to add relevant dependencies to your Maven and Gradle projects to add support for Spring Boot Developer Tools. Maven/Gradle should have an introduction section first:
Maven pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
Gradle build.gradle
Here is the code for Gradle:
dependencies { compileOnly("org.springframework.boot:spring-boot-devtools") }
But this is not how we will add dependencies to test the auto-reload feature of developer tools. We will use the Spring Initializr website (https://start.spring.io/) to generate the project stub based on the options you choose. Here are the steps we'll follow:
- You can go ahead with default options or make your own choices. You can select the build tools (Maven or Gradle), language (Java, Kotlin, or Groovy), and Spring Boot version of your choice.
- After that, you can add necessary dependencies by clicking on the ADD DEPENDENCIES… button and selecting the dependencies required for your application.
- I have chosen the default options and added
spring-boot-starter-web
,spring-boot-dev-tools
, and Thymeleaf as dependencies for my demo Hello World Spring Boot application. - Now, go ahead and click on the GENERATE button to download the generated source code on your computer. Here is the screen you should see:
Figure 1.4 – Spring Initializr home page
- After the download, you can import the project to your IDE.
The next logical step is to build a simple Hello World Spring Boot web application. Let's begin.
Anatomy of the Spring Boot web application
The best way to understand the working parts of the Spring Boot application is by taking a look at an example. In this example, we will create a simple Spring Web MVC application that will accept HTTP GET requests at http://localhost:8080/hello
. We will get an HTML web page with "Hello, John!" in the HTML body in response. We will allow the user to customize the default response by entering the query string in the http://localhost:8080/hello?name=Jack
URL so that we can change the default message. Let's begin:
- First, let's create a
HelloController
bean using the@Controller
annotation for handling incoming HTTP requests. The@GetMapping
annotation binds the HTTP GET request to thehello()
method:@Controller public class HelloController { @GetMapping("/hello") public String hello(@RequestParam(defaultValue = "John", name = "name", required = false) String name, Model model) { model.addAttribute("name", name); return "index"; } }
This controller returns the name of the view, which is
index
in our case. The view technology we have used here is Thymeleaf, which is responsible for server-side rendering of the HTML content. - In the source code template,
index.html
is available under the templates folder insrc/main/resources/
. Here are the contents of the file:<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>Welcome</title> </head> <body> <p th:text="'Hello, ' + ${name} + '!'" /> </body> </html>
- Spring Boot provides an opinionated setup for your application, which includes a
main
class as well:@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
- We will run our application using
mvn
spring-boot:run maven goal
, which is provided byspring-boot-maven-plugin
:Figure 1.5 – Spring Boot application startup logs
Note
To reduce the verbosity of the logs, we have trimmed them down to show only the parts that are relevant to our discussion.
If you observe the logs carefully, we have developer tools support enabled, an embedded Tomcat server listening at port
8080
, and an embedded LiveReload server running on port35279
. So far, this looks good. Once the application is started, you can access the http://localhost:8080/hello URL.Figure 1.6 – REST endpoint response
- Now we will do a small code change in the Java file and save it and you can see from the logs that the embedded Tomcat server was restarted. In the logs, you can also see that the thread that has spawned the application is not a main thread instead of a
restartedMain
thread:2021-02-12 16:28:54.500 INFO 53622 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2021-02-12 16:28:54.500 INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2021-02-12 16:28:54.501 INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms 2021-02-12 16:29:48.762 INFO 53622 --- [ Thread-5] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' 2021-02-12 16:29:49.291 INFO 53622 --- [ restartedMain] c.e.helloworld.HelloWorldApplication : Started HelloWorldApplication in 0.483 seconds (JVM running for 66.027) 2021-02-12 16:29:49.298 INFO 53622 --- [ restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged 2021-02-12 16:29:49.318 INFO 53622 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2021-02-12 16:29:49.319 INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2021-02-12 16:29:49.320 INFO 53622 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
This completes the demo of the auto-restart feature of the Spring Boot Developer Tools. We have not covered the LiveReload feature for brevity as it would be difficult to explain here because it all happens in real time.
JRebel
JRebel (https://www.jrebel.com/products/jrebel) is another option for Java developers for accelerating their inner loop development process. It is a JVM plugin, and it helps in reducing time for local development steps such as building and deploying. It is a paid tool developed by a company named Perforce. However, there is a free trial for 10 days if you would like to play with it. It provides the following functionalities:
- It allows developers to skip rebuild and redeploys and see live updates of their changes by just refreshing the browser.
- It will enable developers to be more productive while maintaining the state of their application.
- It provides an instant feedback loop, which allows you to test and fix your issues early in your development.
- It has good integration with popular frameworks, application servers, build tools, and IDEs.
There are many different ways to enable support for JRebel to your development process. We will consider the possibility of using it with an IDE such as Eclipse or IntelliJ. For both IDEs, you can install the plugin, and that's it. As I said earlier, this is a paid option, and you can only use it for free for 10 days.
For IntelliJ IDEA, you can install the plugin from the marketplace.

Figure 1.7 – IntelliJ IDEA installing JRebel
For the Eclipse IDE, you can install the plugin from Eclipse Marketplace.

Figure 1.8 – Eclipse IDE installing JRebel
Since JRebel is a paid option, we will not be exploring it in this book, but you are free to test it yourself.
We have covered the traditional application development inner loop life cycle and tools such as Spring Boot Developer Tools and JRebel, which allow rapid application development. Let's now go through the container-native application development inner loop life cycle.
Checking out the container-native application development inner loop
Kubernetes and containers have introduced a new set of challenges and complexities to the inner development loop. Now there are an additional set of steps added to the inner loop while developing applications, which is time-consuming. A developer would prefer to spend time solving business problems rather than waiting for the build process to complete.
It involves steps such as the following:
- A developer making code changes in an IDE
- Building and packaging the application
- Creating a container image
- Pushing the image to a container registry
- Kubernetes pulling the image from the registry
- Kubernetes creating and deploying the pod
- Finally, testing and repeating
Engineers at Google call this an infinite loop of pain and suffering. Here is a visualization of the container-native application development inner loop:

Figure 1.9 – Container-native application development inner loop
As you can see, we now have three more steps added to the inner development loop, that is, creating a container image of your application, pushing it to a container registry, and finally, pulling the image while deploying to a container orchestration tool such as Kubernetes.
The container image could be a Docker or OCI format image, depending on the tool you use to build your images. You have options such as Docker Hub, AWS Container Registry, Google Container Registry, or Azure Container Registry for the container registry. Then, finally, in deployment, for your container orchestration, you have tools such as Kubernetes, which will first pull the image from the container registry and deploy your application.
There are many manual steps involved here. It also depends on what tools you have used for the local development workflow. For instance, you will use commands such as the following:
docker build docker tag docker push kubectl apply
The following are the detailed steps that a developer has to go through while developing container-native applications:
- Defining how to configure the OS for your container with a Dockerfile
- Defining the packaging of your application into a container image by adding instructions to the Dockerfile
- Creating a container image with Docker commands such as
docker build
anddocker tag
- Uploading the container image to a container registry with a command such as
docker push
- Writing one or more Kubernetes resource files in YAML
- Deploying your application to the cluster with commands such as
kubectl apply -f myapp.yaml
- Deploying services to the cluster with commands such as
kubectl apply -f mysvc.yaml
- Writing the config so that apps can work together with commands such as
kubectl create configmap
- Configuring apps to work together correctly with commands such as
kubectl apply -f myappconfigmap.yaml
Wooh!!! That's a lot of steps and a time-consuming process. You can use scripting or docker compose
to automate it to some extent, but soon you will realize that it cannot be fully automated without a tool such as Skaffold, which can abstract away many things related to building and deployment.
In Chapter 3, Skaffold – Easy-Peasy Cloud-Native Kubernetes Application Development, we will cover Skaffold, which simplifies the process we have covered here with a single command. My only intention here was to give you an idea of the steps involved. We will cover these steps with some hands-on examples in the next chapter.
Summary
In this chapter, we have covered many topics, such as what a typical inner development loop is and its importance. We have also discussed how both the inner and outer development loops are different, and then we explored whether the CI/CD process can act as a replacement for the inner development loop.
We then discussed the steps involved in the traditional application development inner loop and we covered tools such as Spring Developer Tools and JRebel, which make the application development a lot easier. To explain this further, we created a simple Spring Boot web MVC application. Finally, in the last section, we covered the container-native application development inner loop. We also covered the steps involved in container-native application development.
In this chapter, the focus was on introducing you to concepts such as inner and outer development. You can use Spring Boot Developer Tools and JRebel to accelerate/automate your traditional application development life cycle.
In the next chapter, we will cover the problems a developer faces while developing an application with Kubernetes.
Further reading
- Learn more about Spring Boot Developer Tools at https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/html/using-boot-devtools.html.
- More information on JRebel is available at https://www.jrebel.com/.
- Learn more about Docker from Docker for Developers, published by Packt (https://www.packtpub.com/product/docker-for-developers/9781789536058).
- Learn more about Kubernetes from Mastering Kubernetes, published by Packt (https://www.packtpub.com/product/mastering-kubernetes/9781786461001)