Chatgptでシステム構築 : Deploying a Spring Boot Native‑Image(Docker Container basis) on an AWS Ubuntu Server

🔳 ＜テーマ＞Chatgptで、システム構築

使用技術: Spring Initilizer, Springboot, IntelliJ, GraalVM for Native Image, Docker Container

使用環境　: Mac M1(Arm), Unbuntu 20 on AWS

🔳＜やりたい事＞
Spring Initilizar(https://start.spring.io/) でSpringbootアプリ作成して、
→ それを手元のMac M１マシン（Arm basis)にGraalVM CEを使い、Native Image化して（開発環境はIntelliJ）
→ それをMac上で、Docker Containerに変換して
→ 出来上がったDocker ContainerをAWS上のUbuntu20インスタンスのDocker環境に移動して
→ VMインスタンス上で、portをTCP8080にマップして、Docker Containerを起動させて、
→　手元MacPCから、アクセスして起動確認する。

🔳＜結果＞成功
```
naive@naive ~ % curl http://152.xxx.xxx.xxx:8080/hello

Hello from Native Image!%      
```

🔳＜所感＞
Chatgptを駆使する事で、複数の技術が介在するシステム構築を瞬間的に構築可能であることが判明した。
Chatgptに投げ込んだ指示文は下記。

```java:chatgpt
I am now writing instruction sheet how to setup Docker Container including Native Image on Ubuntu Server on OCI.
Can you give me it ?
The procedure might be below.

1.  On Mac PC(Arm based M1), Springboot app is created using Spring intilizer. (app name is "demo".)
2.  Springapp is converted to native image file using GraalVM CE on my Mac PC.(IntelliJ environment)
3.  Native image file is compiled to Docker Image on my Mac PC. (Rancher Desktop is used.)
4.  Docker image is copied to the home directory of Ubuntu 20 (Arm base)  running on AWS. (I used termius SSH client)
5.  On Unbuntu VM, docker environment setup is done and firewall is opened(TCP8080) along with VCN Network Security setup.
6.   Docker images is run on Unbuntu VM on AWS. (Show the command to run docker image and map the port to TCP8080).
7.   I access to docker from my Mac PC's browser using "http://<server ip address>:8080/hello " 

I believe that that is all but if something is missing, please add. 
```

=====================
＜下記詳細＞

1. 
Create the Spring Boot Application with IntelliJ (Using Gradle) 
Spring Initilizar (https://start.spring.io/)

・Open IntelliJ IDEA.
・Create a New Project:
・Choose Spring Initializr as the project type.
・Select your installed JDK (Java 17 or later).
・Configure the following settings:
・Group: com.example
・Artifact: demo
・Name: demo
・Build System: Gradle
・Language: Java
・Spring Boot Version: 3.4.3 (or the latest stable 3.x)
・Select Dependencies:
At a minimum, add Spring Web.
Optionally add Spring Boot Actuator.

Finish and Open the Project.

・Configure the Application:（On IntelliJ)
In src/main/resources/application.properties, add:

```java:application.properties
server.address=0.0.0.0
```

This ensures that when running inside a container, your app listens on all network interfaces.

２.
Configure the Gradle Build for Native Image
Open the generated build.gradle file and update it as follows:

```java:build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.3'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'org.graalvm.buildtools.native' version '0.10.5'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}

// We are using dynamic linking, so do NOT force static linking.
graalvmNative {
    binaries {
        main {
            // No extra buildArgs needed.
        }
    }
}
```

demo/src/main/java/co/example/demo/DemoApplication
```java:DemoApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;


@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

@RestController
class HelloController {

	@GetMapping("/hello")   //この"hello"が、呼び出しDirectoryになる。勿論、変更も可。
	public String hello() {
		return "Hello Bull from Native Image!";
	}
}


```

３.
Build the Native Image Using GraalVM

Install GraalVM CE:
Download GraalVM CE for Java 17 from GraalVM CE Downloads and install it.→　https://www.graalvm.org/downloads/#

Install the Native Image Component:
```java:terminal
gu install native-image
```
尚、Windowsではこちらの様です。
```java:Command Prompt
gu.cmd install native-image
```

Build the Native Image:
Open a terminal in your project root (or use IntelliJ’s Terminal) and run:

```java:terminal
./gradlew clean nativeCompile
```


The native executable (typically named demo) will be generated under build/native/nativeCompile/.

Check on local Mac PC wether to run normally or not.
```java:terminal
% cd /Users/naive/Downloads/demo/build/native/nativeCompile
% ./demo
```

４.
Build a Docker Image Containing the Native Image (Using Rancher Desktop)
Create a Multi-Stage Dockerfile:
Save the following content as Dockerfile in your project root:

```java:Dockerfile
# -------------------------------------------
# 1) Builder Stage: Compile the Native Image
# -------------------------------------------
FROM ghcr.io/graalvm/native-image:latest as builder

# Switch to root to install required utilities
USER root
RUN microdnf install -y findutils \
    || yum install -y findutils \
    || dnf install -y findutils \
    || (apt-get update && apt-get install -y findutils) \
    || apk add --no-cache findutils

WORKDIR /app
COPY . .
RUN chmod +x gradlew
RUN ./gradlew clean nativeCompile

# -------------------------------------------
# 2) Final Stage: Create the Runtime Container
# -------------------------------------------
FROM ubuntu:22.04

# Install zlib to ensure libz.so.1 is available (required by the dynamically linked native image)
RUN apt-get update && apt-get install -y libz1

# Copy the native executable from the builder stage
COPY --from=builder /app/build/native/nativeCompile/demo /app/demo

# Expose port 8080
EXPOSE 8080

ENTRYPOINT ["/app/demo"]
```

Build the Docker Image:

Open a terminal in your project directory and run:
```java:terminal
docker build -t demo --no-cache .
```
(Rancher Desktop provides the Linux container environment on your Mac.)

🔳 Check whether your Docker Container can work locally.

```java:terminal
naive@naive ~ % docker images
REPOSITORY                                          TAG                    IMAGE ID       CREATED          SIZE
demo                                               latest                 e46212f8a572   39 minutes ago   201MB
rancher/mirrored-library-busybox                    1.36.1                 3fba0c87fcc8   21 months ago    4.04MB
rancher/mirrored-coredns-coredns                    1.10.1                 97e04611ad43   2 years ago      51.4MB
rancher/mirrored-pause                              3.6                    7d46a07936af   3 years ago      484kB
ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy   latest                 2f2948f1d6e9   292 years ago    4.78MB
```

Run your docker. Don't forget your Rancher Desktop is running on backgroud.

```java:terminal
docker run --rm -p 8080:8080 demo
```

Export the Docker Image to a Tar File:

Once the build is complete, run:
```java:terminal
docker save -o demo.tar demo
```

５.
Transfer the Docker Image to Your Ubuntu Server (ARM-based on AWS)
Ensure your Ubuntu VM is ARM-based:
This is very important. Your target Ubuntu server on AWS must be running on ARM architecture (e.g., AWS Graviton instances). Verify the architecture by running:
```java:linux
uname -m
```

It should return something like aarch64 or arm64.

Transfer the Image:
Use SCP (or your preferred secure transfer tool) to copy demo.tar to your Ubuntu server:
```java:terminal
scp demo.tar your_username@your-ubuntu-server-ip:~/
```
(You can use an SSH client like Termius.)

Part II – On Your AWS Ubuntu Server (ARM-based)

６.
Install and Set Up Docker on the Ubuntu Server
Update the Package Index:
```java:linux
sudo apt-get update
```
Install Required Packages:
```java:linux
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
```
Add Docker’s Official GPG Key:
```java:linux
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
```

Add Docker’s Repository:
```java:linux
sudo add-apt-repository \
  "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable"
```

Update the Package Index Again:
```java:linux
sudo apt-get update
```

Install Docker Engine:
```java:linux
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
```

Start and Enable Docker:
```java:linux
sudo systemctl start docker
sudo systemctl enable docker
```

(Optional) Allow Your User to Run Docker Commands Without Sudo:
```java:linux
sudo usermod -aG docker $USER
```
Then log out and log back in (or reboot).

Verify Docker Installation:
```java:linux
docker --version
sudo docker run hello-world
```

７.
Open Port 8080 on Your Ubuntu Server
Using UFW (if enabled):
```java:linux
sudo ufw allow 8080/tcp
sudo ufw status
```

Using iptables:
```java:linux
sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save
```

８.
Load and Run the Docker Image on the Ubuntu Server
Load the Docker Image:
```java:linux
sudo docker load -i ~/demo.tar
```

Verify that the image is loaded:
```java:linux
sudo docker images
```

Run the Docker Container:
```java:linux
sudo docker run --rm -p 8080:8080 demo:latest
```
This command maps the container’s port 8080 to the server’s port 8080.

Test Your Application: From your local Mac PC (or from another machine that can reach it), run:
```java:terminal
curl http://<your-ubuntu-server-ip>:8080/hello
```
You should see the expected output, for example: "Hello from Native Image!"

９.
Access the Application from Your Mac
Open your web browser on your Mac and navigate to:
```java:linux
http://<your-ubuntu-server-ip>:8080/hello
```
You should see your Spring Boot application’s response.

🔳 Summary
```java:linux
On Your Mac (M1):

・Create a Spring Boot app using IntelliJ with Gradle.
・Configure the Gradle build for native image support.
・Build the native image using GraalVM.
・Package the native image in a Docker image using a multi-stage Dockerfile (with Ubuntu 22.04 as the final stage).
・Export the Docker image as demo.tar.

Transfer the Image:

・Use SCP to copy demo.tar to your AWS Ubuntu server.
・Important: The Ubuntu VM must be ARM-based (e.g., AWS Graviton) to match the image architecture.

On Your AWS Ubuntu Server (ARM-based):

・Install and set up Docker.
・Open port 8080 in the firewall.
・Load the Docker image from demo.tar and run the container.
・Test the application locally, then access it remotely from your Mac.
```