Docker-SpringBoot加PostgreSQL之compose打包教學
By: 陳冠億 Kenny
Tags: Postgres, Spring Boot, Backend Development, Docker, Back End Development

今天要講解的這篇文章主要是教學如何透過Docker執行docker-compose指令一次啟動多台container。也就是同時啟動Springboot的Backend Service+PostgreSQL+pgAdmin的範例。這算是後端服務啟動的基本款，當我們要在一台主機架設後端服務的話，勢必會用到Backend Service + Database + Database GUI，才方便我們做後續的維護。

先來解釋名詞囉！

何謂Docker?

先來談談為什麼我覺得要使用Docker呢，有以下幾點：

若我們要在一台完全乾淨的主機部署我們寫好的service，首先你可能要先在這台主機裝一堆環境，例如：程式語言環境、資料庫環境、資料庫管理介面環境、一堆你的程式額外依賴的套件等等。等於說我們要先裝好這些環境，並且還要實際運行看看才知道能不能正確的運行。在這期間會一直try and error，其實是非常耗費時間的。

再來，作業系統又是一大考量點，Windows、Linux兩者的部署方式差距很多，你的service所需要依賴的環境也會因應作業系統需要去做調整。再來…同樣的作業系統又有版本的考量，這…會忙到焦頭爛耳。

假設現在做的專案是前後端分離好了，後端工程師寫好了service，想要給前端工程師直接在主機上使用，總不可能叫前端工程師裝一堆後端service所需要的套件吧。等裝好還要再測試環境，等到天荒地老。

所以，綜合以上幾點，我們可以知道Docker最大功用，就是自動化一鍵部署！可以輕輕鬆鬆使用簡單的指令就把我們想要的服務在主機在架設起來，而採用的技術叫做container，簡單來說就很像是輕量化的虛擬機，啟動速度比虛擬機快多了，透過在container啟動服務，可以跟主機環境互相連結，讓服務外連出去。

通常用Docker會需要寫Dockerfile，也就是今天我們寫好了Dockerfile，之後不管換到哪台主機，Dockerfile的內容都能夠沿用，只要那台主機裝Docker環境就可以了，就能夠透過Dockerfile讓Docker自動幫我們裝好我們所需要的環境，然後運用幾個指令幫我們啟動service。

docker-compose指令

為什麼會需要docker-compose？假設我要有Backend Service + Database + Database GUI，這些服務啟動，但是一個Dockerfile只能做成一個image，而我們需要這一個image去開起一個container，但我們不可能把三個服務都寫在同一個Dockerfile，因為每個service有它需要設定port、啟動的指令等等，寫在一起太麻煩了，也不推薦，而且通常一個container就是包含一個service，越單純越好，後續作維護也比較方便。

所以docker-compose可以幫助我們build好需要的image，在一一開啟需要的container，可以說是更方便的一鍵部署的方式！

以下開始示範如何操作~

先準備好Springboot測試專案

專案內容：連接PostgreSQL，透過Restful API秀出Table裡面的資料。

假設專案名稱叫做docker-demo。

建立Person的Class，作為PostgreSQL裡面的table叫做Person。

@Entity

public class Person { public class Person { @Id



private Long id;

private String name;

private String password;

private String email; @GeneratedValue (strategy = GenerationType.IDENTITY)private Long id;private String name;private String password;private String email; public Person(String name, String password, String email) {

this.name = name;

this.password = password;

this.email = email;

} public Person() {

} public Long getId() {

return id;

} public void setId(Long id) {

this.id = id;

} public String getName() {

return name;

} public void setName(String name) {

this.name = name;

} public String getPassword() {

return password;

} public void setPassword(String password) {

this.password = password;

} public String getEmail() {

return email;

} public void setEmail(String email) {

this.email = email;

}

}

建立PersonRepository的interface，來藉此使用JPA ORM的框架功能。

@Repository

public interface PersonRepository extends JpaRepository<Person, Long> { public interface PersonRepository extends JpaRepository { }

建立Controller的class，建立Restful API。

@RestController

("/api")

public class Controller { @RequestMapping ("/api")public class Controller { @Autowired

private PersonRepository personRepository; private PersonRepository personRepository;

public List<Person> getAllPerson() {

return personRepository.findAll();

}

} @GetMapping ("/people")public List getAllPerson() {return personRepository.findAll();

也就是建立GET METHOD的路由來取得所有person的資料~

設定application.properties

# PostgreSQL連線設定

spring.datasource.url=jdbc:postgresql://db:5432/postgres

spring.datasource.username=postgres

spring.datasource.password=root

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false

# 服務啟動就自動執行schema.sql

spring.datasource.initialization-mode=always

spring.datasource.schema=classpath:schema.sql

spring.datasource.data=classpath:data.sql

前半段就是連線PostgreSQL的設定，在這邊假設我們的database名稱叫做postgres，也就是PostgreSQL預設就會有的資料庫名稱。

請注意有個地方可能會覺得疑惑：

為什麼連線的url是採用db:5432的方式，而不是填localhost，或是其他有意義的IP。沒錯，如果你是要在自己的主機上做測試，的確是要改成localhost，但現在不是阿，我們是要透過docker去幫我們建立，那這個db名稱怎麼來的，留在後續講docker-compose就知道了。

後半段的設定就是當我們一啟動Springboot Service，可以幫助我們自動建立資料庫表格及輸入資料進去。

所以我們需要在resources資料下輸入新增兩個檔案，分別是schema.sql、data.sql

schema.sql

CREATE TABLE IF NOT EXISTS person (

id SERIAL PRIMARY KEY,

name varchar(11) NOT NULL ,

password varchar(100) NOT NULL,

email varchar(100) NOT NULL

);

data.sql

INSERT INTO person (name, password, email) VALUES ('Kenny', '0000', 'Kenny@example.mail');

INSERT INTO person (name, password, email) VALUES ('Nicole', '0000', 'Nicole@example.mail');

INSERT INTO person (name, password, email) VALUES ('Jack', '0000', 'Jack@example.mail');

INSERT INTO person (name, password, email) VALUES ('Mary', '0000', 'Mary@example.mail');

INSERT INTO person (name, password, email) VALUES ('David', '0000', 'David@example.mail');

設定pom.xml

我們需要在properties裡面加入這段：

<properties>

<skipTests>true</skipTests>

</properties>

這個作用是方便當我們要利用maven將專案build成jar檔案的時候，跳過一些檢查，例如如果我們現在build的話是不會成功的，因為剛剛我們資料庫連線的url是寫db:5432，但是我們還沒用docker所以，是會連不上資料庫的，導致build失敗。導致build失敗。因此我們需要加入這行來避免build失敗。

接著我們就可以利用maven來build出專案的jar檔案，這樣我們就可以透過啟動jar檔案來啟動我們的service。這邊建議大家採用Intellij IDEA來做開發測試喔，不然還需要特別去maven網站下載maven套件到主機進行build的操作。這邊我是直接使用Intellij內建maven套件直接幫我們build就可以了。如下圖：

點擊package指令即可。成功的話會得到以下畫面：

接著我們就可以在當前專案根目錄，有一個target目錄，點進去裡面會產生一個docker-demo-0.0.1-SNAPSHOT.jar，名稱會依你的專案名稱而定。將這個jar檔案複製並放置在專案的根目錄下。

當以上的動作都搞定後，也就完成了Springboot測試專案的流程了。

準備Dockerfile

接著我們需要定義Springboot專案的Dockerfile，新增Dockerfile在專案的根目錄下，並輸入以下內容：

FROM adoptopenjdk/openjdk11:latest

COPY docker-demo-0.0.1-SNAPSHOT.jar app.jar

非常簡單，就兩個指令即可，要做更複雜的設定可以在docker-compose去做設定就可以了。

這邊也就是我們需要JDK的環境，這樣才可以執行我們的Springboot專案！再來將我們剛剛的jar檔案，複製到docker裡面的環境，並命名為app.jar。詳細的Dockerfile指令這邊就不多講了，因為會花費許多時間，有時間再另外發docker的教學文。

準備docker-compose.yml

再來，需要定義docker-compose.yml，因為將其新增在專案的根目錄下，並輸入以下內容：

version: '3'

services:

db:

container_name: 'postgres'

image: postgres

environment:

POSTGRES_PASSWORD: root

volumes:

- pgdata:/var/lib/postgresql/data/

ports:

- "5432:5432"

dbGUI:

container_name: 'pgadmin'

image: dpage/pgadmin4

environment:

PGADMIN_DEFAULT_EMAIL: "kenny@example.com"

PGADMIN_DEFAULT_PASSWORD: "kenny"

ports:

- "16543:80"

depends_on:

- db

web:

container_name: 'spring-docker-demo'

build: ./

image: spring-docker-jar:latest

entrypoint: ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

restart: always

ports:

- "8080:8080"

depends_on:

- db

volumes:

pgdata:

這邊的設定就是一次設定好Backend Service、Database、Database GUI。那你可能會好奇說怎麼不用寫Database或Database GUI的Dockerfile，是可以的，但是我認為並沒有太大的意義，因為Database跟Database GUI我們只需要image就可以了，並不用做其他複雜的動作，所以在docker-compose這邊一次設定就可以了。

首先一一來講解services：

db

這個就是我們的PostgreSQL的設定，可以設定之後的container_name，還有去抓取怎樣的image。在這邊透過docker-compose，Docker可以自動幫我們抓取PostgreSQL的image。接著environment，是PostgreSQL會吃的環境變數，最重要的就是要定義POSTGRES_PASSWORD，當然也可以定義POSTGRES_USER，但這邊PostgreSQL預設的USER就是postgres，加上我們在Springboot專案那邊也是採用預設的設定，因此這邊只需要定義PASSWORD即可，那基本上就是要跟我們在Springboot專案那邊定義的一模一樣。而ports，採用5432:5432，也就是將主機的5432 PORT MAPPING到container裡面的5432 PORT。

在這邊相信大家也了解為什麼我在Springboot專案裡面連線資料庫的url要寫db，沒錯那個名稱就是service的名稱。因為實際上Backend Service是在一個container，Database也是在一個container，兩個container要互相連線的話，在這邊docker-compose會自動幫我們做一個link的動作，但也就是說host這個要改成service的name，docker container這邊會自動去mapping。

dbGUI

這個基本上就是，在維護的時候總不可能去下命令列慢慢看當前資料庫的資料有哪些吧，因此還是需要資料庫介面來幫我們做操作，那基本上比較特別的是它的environment這邊，PGADMIN_DEFAULT_EMAIL跟PGADMIN_DEFAULT_PASSWORD內容自己取就可以了。它跟一般主機版本的pgAdmin有點不一樣，我也是在Docker第一次用才發現的。至於它的ports，內部是直接開放80 PORT的。主機的PORT也是自己決定就可以了。不要跟妳主機上的服務或是其他container上的服務衝到就好。

在這邊要特別注意的是depends_on：

這個功用是告訴Docker說，一定要db service先執行，才會在執行dbGUI service。可以理解成，一定是資料庫服務先搞定，你的資料庫管理介面服務再啟動才有意義。你的資料庫管理介面服務再啟動才有意義。但是要注意的是，這個所謂的啟動service的順序，並不是會等到service完全啟動成功，在開啟下一個service。那是需要用額外的指令，但基本上也可以不用那麼麻煩，假設第一次沒成功連線成功就有可能是這個問題，再重新啟動conatiner重新連線就可以了。

web

這個就是我們jar檔案，開啟的Backend Service。其中build指令就代表說，去哪裡執行我們的Dockerfile，沒錯也就是說，它可以幫我們先自動執行Dockerfile，取得Backend Service需要的環境後，在幫我們建立container。而entrypoint可以想成是服務的啟動點，也就是要怎麼執行jar檔案。這個指令就不多加講解了。restart基本上，就是如果有遇到錯誤，service可以在自動重啟再去做check的動作。

要注意的是，我們的web也是需要依賴db這個service優先開啟的。

最後沒講的volumes指令，這個簡單來說就是主機上的硬碟可以跟我們container裡面的虛擬空間互相連結，也就是說當container刪掉了，這個空間並不會被刪掉，會一直留著，畢竟它就在你主機上的硬碟上而已，除非你主動去刪除，那基本上這個功用就是用來保存資料庫的資料，不然每次我只是要開個container去做測試，或是我的container壞掉了要重用，那我的資料庫的資料我還要先export出來，最後import進去，實在太麻煩了，只要有個共用空間讓資料庫可以去存取你的資料就可以一直用下去了！

那基本上如上面的內容的話是寫成：

pgdata:/var/lib/postgresql/data/

冒號後面的路徑代表的是container內部存放路徑，那基本上每個資料庫都會有預設的data的位置，PostgreSQL就用上述路徑就可以，而冒號前面的路徑名稱，就是我們自己自訂了。那可能會好奇究竟這個pgdata路徑在哪裡呢？這之後我們啟動container後就會知道了。

運行docker-compose指令

好的，當以上手續都準備好後，就能運行docker-compose指令。至於你的主機如何安裝docker那些等設定這邊就不說了，之後會各發一篇是Windows10安裝Docker教學及Ubuntu安裝Docker教學，敬請期待。

那我這邊是用windows的環境去運行Docker指令。

在專案的根目錄下，執行以下指令：

docker-compose up -d

也就是叫Docker去執行你的docker-compose.yml並且，啟動多台container在背景執行！完成一鍵部署！

要注意的是，如果你的docker裡面沒有這些image檔案，那麼部署起來會需要一些時間的。

部署成功後，基本上會得到以下訊息：

接著我們運行以下docker指令，確認是否三個container服務有沒有啟動成功：

docker ps -a

如果成功的話，應該會得到以下的畫面：

就是這麼神奇～輕輕鬆鬆，不需要在主機上灌一堆環境，就這樣部署完成你的後端所有服務！

接著要知道有沒有成功呢，很簡單，還記得我們專案是有個RestfulAPI，我們來連線看看！

在主機上輸入以下網址：

localhost:8080/api/people

會得到以下內容：

沒錯，這就是你資料庫裡面的內容，有沒有～

你可能還是不相信就這麼簡單就部署起來，那我們去資料庫介面看資料庫到底有沒有資料～

輸入以下網址：

localhost:16543

會得到以下畫面：

並輸入你的設定好的帳號密碼進行登入即可。

你會覺得怎麼是空空如也，別急，我們需要將pgAdmin去連線我們的資料庫才行啊！

主機名稱就是service名稱，也就是db。其他資訊就是按照我們在docker-compose.yml怎麼定義的，就怎麼填進去就可以了。點擊右下角的保存，即可連線資料庫！

沒錯就是如上圖所示，你發現person table裡面真的有資料～

以上就是SpringBoot加PostgreSQL之compose打包教學囉！

接著我們來進行一個小實驗，來驗證說究竟volume這個指令有沒有用處，是不是我移除了container那我的資料庫資料也就不見了呢？執行以下指令：

docker-compose down

這個指令也是非常方便，一鍵將我們三個container一次移除乾淨，但是image是會保留的喔。

接著我們在運行一鍵部署的指令：

docker-compose up -d

我們在去看資料庫的資料，理論上，原本有五筆資料，只要我們volume沒被刪掉的話，那我們理論上啟動Backend Service的時候會自動塞入五筆，那所以現在person table資料應該要有十筆喔！

沒看錯，就是十筆，也就是我們這邊就可以知道volume的好處，只要我們移除的container的時候不移除volume，那麼這個路徑的資料就會一直保存著。所以今天當我的資料庫服務掛掉了怎麼辦?我需要把container移除再重新建立，再也不用擔心資料會不見囉。

最後如果想要在移除所有container順便移除volume的話，執行以下指令即可：

docker-compose down --volume

那麼當你在重新重啟container後，你會發現資料庫只會有五筆資料而已。

總結

以上就是所有SpringBoot加PostgreSQL之compose打包教學，當然撰寫Dockerfile及docker-compose.yml還有很多指令及細節去一一注意，有機會再發專屬Docker新手教學文章。畢竟這東西，如果一段時間沒寫，要查指令也是很麻煩的！

所有細節的source code可以在我的Github參考：https://github.com/KennyChenFight/spring-docker-demo

可以來我的個人部落格參觀唷： https://kennychen-blog.herokuapp.com/