Tingkatkan Kemampuan Deployment dengan Docker
Secara sederhana, deployment adalah semua proses yang dilakukan sedemikian hingga aplikasi kita dapat berjalan pada suatu server dan dapat diakses oleh publik. Proses ini akan melibatkan berbagai tahapan berupa tahapan konfigurasi, instalasi berbagai tools yang diperlukan, membangun aplikasi, testing aplikasi, dan menjalankannya pada server yang dapat diakses secara publik. Tentunya, kemampuan deployment merupakan salah satu kemampuan yang harus dimiliki oleh programmer. Namun, proses deployment sendiri tidaklah semudah itu. Oleh karena itu, pada blog ini, saya akan menjelaskan hal berikut.
- Deployment dengan platform as a service (PaaS) sebagai cara yang paling mudah untuk memulai deployment.
- Kelebihan dan kelemahan deployment dengan PaaS.
- Deployment dengan Docker.
- Kelebihan dan kelemahan deployment dengan Docker.
- Studi Kasus Docker untuk Next.js
Yuk, kita simak!
Deployment dengan Platform as a Service (PaaS)
Platform as a Service adalah hosting provider yang menyediakan layanan untuk men-deploy aplikasi kita ke server yang dikelola oleh mereka. Dengan memanfaatkan PaaS, kita dapat dengan mudah men-deploy aplikasi kita dan PaaS akan mengelola banyak detail tingkat rendah seperti server dan konfigurasinya. Hal ini memungkinkan kita sebagai programmer untuk fokus membangun aplikasi kita dibandingkan dengan mengonfigurasi dan mengelola server tempat aplikasi tersebut berjalan. Berdasarkan penjelasan tersebut, kita dapat memperoleh berbagai keuntungan ketika ingin memulai deployment dengan mudah dan cepat.
Secara sederhana, PaaS memiliki 3 komponen utama untuk mendukung proses deployment: instance dari aplikasi, servis database terintegrasi, nama domain dengan protokol HTTPS. Pertama, instance dari aplikasi dapat dianggap sebagai mesin virtual yang menjalankan aplikasi kalian. Kedua, servis database terintegrasi memungkinkan kita untuk menyiapkan database untuk aplikasi kita secara mudah karena proses persiapan dan konfigurasi akan dilakukan oleh PaaS. Untuk menggunakan servis database terintegrasi, biasanya kita harus mengikuti ketentuan dari PaaS provider. Terakhir, nama domain dengan protokol HTTPS akan langsung disediakan untuk aplikasi kita dan dapat kita custom, seperti https://myapp.adaptable.app
. Jika kita mempersiapkan HTTPS secara manual, ada banyak proses yang harus dilakukan seperti mempersiapkan sertifikat TLS untuk situs web kita.
Ada beberapa PaaS yang dapat kalian gunakan, seperti Fly.io, Railway.app, Adaptable.io, Render, dan Heroku. Oleh karena itu, kita harus memilih PaaS yang sesuai dengan kebutuhan kita. Perhatikan bahwa masing-masing PaaS akan memiliki kelebihan, kekurangan, dan fitur-fitur yang berbeda-beda antara satu sama lain. Contohnya adalah sebagai berikut.
Adaptable.io sebagai PaaS yang memiliki proses deployment yang cukup mudah. Pada adaptable.io, kita dapat mendeploy aplikasi cukup dengan menghubungkan adaptable.io dengan GitHub repository dari aplikasi yang ingin di-deploy dan mengatur konfigurasi yang sesuai. Kemudian, Adaptable.io akan mengurus proses deployment secara automatis seperti gambar berikut.
Apabila kalian ingin mencoba mendeploy aplikasi docker-nodejs-sample
kalian harus mengikuti aturan dari Adaptable.io terkait struktur proyek express yang diharapkan oleh Adaptable.io. Untuk instruksi lebih lanjut kalian dapat mengakses kedua link: Express App Deploy Template dan Deploying an Express App. Hal ini dikarenakan Adaptable.io mengharapkan kita untuk menggunakan nama environment variable yang telah ditetapkan untuk PostgreSQL jika kita ingin menggunakan database yang disediakan oleh Adaptable.io dan tidak memperbolehkan penggunaan local file-based database seperti SQLite yang merupakan default untuk aplikasi docker-nodejs-sample
.
Kelebihan dan Kelemahan Deployment dengan Platform as a Service (PaaS)
Walaupun PaaS memiliki banyak fitur dan mudah digunakan untuk pemula, PaaS tetap memiliki kekurangan dibandingkan ketika kita men-deploy dengan Docker. Berikut adalah penjelasan singkat mengenai kelemahan dan kelebihan dari PaaS.
Pro:
- Pengurangan overhead: Mengurangi biaya infrastruktur seperti server, sumber daya fisik untuk mengelola infrastruktur, alat baru untuk mendukung infrastruktur, dan hal-hal lain untuk mendukung jalannya aplikasi kita.
- Proses coding yang lebih cepat: Platform PaaS menyediakan berbagai fitur untuk deployment sedemikian hingga kita sebagai programmer dapat fokus ke bagian coding.
- Bayar sesuai kebutuhan: Banyak PaaS yang menawarkan mekanisme pembayaran sesuai dengan pemakaian.
- Fitur tambahan: Mendapatkan akses ke database, monitoring tools, dan log management yang terintegrasi.
Kontra:
- Integrasi: Proses integrasi dengan berbagai tools yang ada akan cukup sulit. Hal ini dikarenakan masing-masing PaaS memiliki dokumentasi dan proses tersendiri untuk mengintegrasikan aplikasi kita dengan aplikasi lain. Selain itu, kita harus memahami dan mengikuti aturan dan kebijakan yang ditetapkan oleh penyedia PaaS.
- Keamanan data dan aplikasi: Karena infrastuktur dikelola oleh PaaS, kita harus mempercayai PaaS tersebut. Hal ini membatasi kontrol kita terhadap keamanan data dan aplikasi.
- Perubahan dari vendor: Vendor dapat mengubah arsitekturnya, yang dapat menyebabkan masalah kompatibilitas. Selain itu, jika kita ingin menguab PaaS, kita harus menyesuaikan dengan PaaS lainnya yang mungkin tidak kompatibel dengan PaaS sekarang.
Deployment dengan Docker
Prasyarat
Deployment memungkinkan aplikasi kita dapat berjalan pada suatu server dan dapat diakses oleh publik. Namun, sebelum proses deployment berlangsung, kita harus memiliki sebuah aplikasi yang ingin kita deploy. Oleh karena itu, pada blog ini, kita akan menggunakan aplikasi sampel dari Docker yang dapat diakses pada link berikut https://github.com/docker/docker-nodejs-sample. Untuk mempermudah proses deployment, kalian dapat mem-fork repositori git tersebut pada GitHub. Pastikan kalian telah menginstal git pada perangkat kalian dan akun GitHub. Selain itu, apabila Anda ingin mengikuti proses deployment docker-nodejs-sample
dengan Docker, Tentunya Anda perlu menginstal Docker pada perangkat kalian.
Tutorial Docker
Setelah mengetahui tentang PaaS dan mungkin mencoba melakukan deployment dengan PaaS tertentu, kita mungkin bertanya apakah kita harus melakukan deployment dengan Docker? Secara sederhana, jawabannya adalah tergantung kebutuhan dari aplikasi kalian. Jika aplikasi kalian cukup sederhana dan penggunaan PaaS sudah mencukupi kebutuhan kalian, penggunaan Docker mungkin tidak diperlukan. Namun, bila kalian memiliki kebutuhan yang lebih spesifik dan PaaS sulit untuk dikonfigurasi demi memenuhi kebutuhan tersebut, kalian dapat menggunakan Docker untuk melakukan deployment. Berikut adalah penjelasan mengenai Docker.
Pada dasarnya, Docker adalah sebuah platform terbuka untuk mengembangkan, mengirim, dan menjalankan aplikasi. Docker membungkus aplikasi kita dengan semua dependencies-nya ke dalam sebuah unit standar yang disebut kontainer. Kontainer ini sendiri bersifat ringan dan portabel, serta dapat berjalan di mesin apa pun yang sudah terinstal Docker. Hal ini membuat Docker ideal untuk pengiriman aplikasi yang konsisten. Selain itu, Docker juga memungkinkan penyebaran dan penskalaan aplikasi yang lebih cepat dibandingkan dengan aplikasi yang tidak menggunakan Docker.
Lalu, apa hubungannya Docker dengan deployment? Secara sederhana, Docker dapat mempermudah deployment karena setiap container dapat dijalankan tanpa harus memperhatikan konfigurasi sistem host. Selain itu, karena Docker container mengandung aplikasi kita dengan semua dependencies-nya, hal ini berarti kita dapat menjalankan container pada berbagai environment yang ada tanpa ada masalah, misalnya pada komputer lokal kita, virtual machine dari berbagai cloud provider seperti GCP dan AWS, bahkan pada platform serverless seperti Google Cloud Run.
Walaupun docker dapat mempermudah proses deployment, kita tetap harus membuat dan menjalankan script sedemikian hingga docker container dapat berjalan pada production environment tertentu. Biasanya hal ini akan dilakukan pada CI/CD pipeline yang telah kita konfigurasikan. Namun, jangan khawatir, proses pembuatan script cukup sederhana berkat CLI yang disediakan oleh Docker.
Setelah kita mengetahui tentang Docker, mari kita mencoba untuk men-deploy docker-nodejs-sample
sedemikian hingga docker container untuk aplikasi tersebut dapat dijalankan pada infrastruktur cloud yang dapat diakses secara publik. Tentunya, proses ini akan melibatkan GitHub repository yang telah kalian fork dan pembuatan CI/CD Pipeline menggunakan GitHub Actions secara sederhana. Yuk, kita simak tahapan berikut!
1. Inisialisasi Aset Docker
Pada tahap ini, kita akan membuat aset-aset (file) dari Docker untuk membantu proses pembuatan container. Di dalam direktori docker-nodejs-sample
, jalankan perintah docker init
pada sebuah terminal.
Berikut adalah penjelasan untuk masing-masing file.
.dockerignore
: file untuk menspesifikkan file dan direktori yang diabaikan pada build context untuk mengoptimisasi proses build image. Secara sederhana, build context adalah kumpulan file dan direktori pada mesin host yang dapat digunakan pada saat proses pembuatan image.Dockerfile
: file yang berisi instruksi untuk membangun image.compose.yaml
: file yang digunakan untuk mengonfigurasikan layanan, jaringan, volume, dll.README.Docker.md
: file markdown yang berisi instruksi sederhana. Kalian dapat menghapus file ini bila tidak diperlukan.
2. Membuat docker image dengan Dockerfile
Kita bisa menggunakan Dockerfile
untuk membuat docker image. Pada dasarnya Dockerfile berisi instruksi tentang bagaimana cara membuat image. Berikut adalah konten dari Dockerfile
dan penjelasannya.
ARG NODE_VERSION=20.11.1
FROM node:${NODE_VERSION}-alpine
ENV NODE_ENV production
WORKDIR /usr/src/app
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --omit=dev
USER node
COPY . .
EXPOSE 3000
CMD node src/index.js
ARG
: instruksi untuk mendefinisikan variabel yang dapat digunakan pada saat build-time. Untuk mengakses variabel ini, kita dapat menggunakan sintaks${<nama_variabel>}
.FROM
: instruksi untuk mendefinisikan tahap build dan image dasar yang digunakan.
Image
node:20.11.1-alpine
menyediakan lingkungan yang ringan dan efisien untuk membangun aplikasi Node.js. Ini mencakup Node.js dan npm (Node Package Manager) bersama dengan alat lain yang diperlukan untuk mengkompilasi, membangun, dan membungkus aplikasi Node.js.
ENV
: instruksi untuk mendefinisikan environment variable.WORKDIR
: instruksi untuk menspesifikkan direktori kerja untuk instruksiRUN
,CMD
,ENTRYPOINT
,COPY
, danADD
yang mengikutinya di dalamDockerfile
.RUN
: instruksi untuk menjalankan perintah pada saat build-time.USER
: instruksi untuk mendefinisikan user name atau user group yang akan digunakan untuk sisa dari suatu tahap build.COPY
: instruksi untuk menyalin file atau direktori baru dari mesin host ke file system dari container. Perhatikan bahwa perintah ini dipengaruhi oleh build context danWORKDIR
.EXPOSE
: instruksi untuk menginformasikan Docker bahwa kontainer mendengarkan port jaringan yang ditentukan pada saat runtime.CMD
: instruksi untuk menjalankan perintah pada saat menjalankan container dari image yang dibuat olehDockerfile
.
Setelah kalian mengetahui hal tersebut, kalian dapat mencoba untuk membuat image dan menjalankan container dari perintah berikut.
- Membuat image dengan
docker build -t docker-nodejs-sample-image .
Perhatikan path .
pada akhir perintah merupakan build context yang menyatakan bahwa kumpulan file dan direktori pada mesin host pada direktori saat ini dapat digunakan pada tahap build oleh builder. Selain itu, kita memberikan nama image yang dibuat berupa docker-nodejs-sample-image
melalui opsi -t atau --tag
.
Untuk melihat daftar image yang ada, kita dapat menjalankan docker image ls
.
2. Menjalankan container dari docker-nodejs-sample-image
yang telah dibuat.
Untuk menjalankan docker pada terminal, kita dapat menjalankan docker run -p 3000:3000 docker-nodejs-sample-image
. Opsi -p
berfungsi untuk mempublikasikan port kontainer ke host. Hal ini bertujuan agar aplikasi yang berjalan pada port 3000
pada container dapat diakses melalui mesin host.
Untuk mematikan container, kita dapat menekan CTRL + C
. Selain itu, kita dapat menjalankan container pada background dengan menjalankan docker run -p 3000:3000 -d docker-nodejs-sample-image
. Kemudian, untuk menampilkan container yang sedang aktif, kita dapat menjalankan docker container ls
dan untuk mematikan container, kita dapat menjalankan docker stop <container_id>
. Berikut adalah contohnya.
3. Menjalankan aplikasi dengan Docker Compose
Docker compose adalah alat untuk mendefinisikan dan menjalankan aplikasi multi-kontainer. Perhatikan konten file dari compose.yaml
sebagai berikut.
services:
server:
build:
context: .
environment:
NODE_ENV: production
ports:
- 3000:3000
Pada file di atas, kita mendefinisikan sebuah service dengan nama server
. Selain itu, kita menggunakan build context berupa current directory dari mesin host. Kemudian, kita mengatur environment variable berupa NODE_ENV
dengan value production dan memetakan port 3000 pada host ke port 3000 di dalam kontainer. Hal ini untuk memungkinkan lalu lintas mencapai layanan yang berjalan di dalam kontainer melalui port 3000 pada mesin host. Jika kita perhatikan, konfigurasi pada compose.yaml
mirip dengan konfigurasi yang telah kita gunakan ketika menjalankan docker build
dan docker run
.
Pada dasarnya, Docker Compose menggunakan build context yang ditentukan (dan Dockerfile) untuk membangun image untuk setiap layanan yang ditentukan dalam docker-compose.yaml
. Untuk menjalankan aplikasi dengan Docker compose, kita dapat menjalankan perintah docker compose up --build
untuk menjalankan container pada terminal dan docker compose up --build -d
untuk menjalankan container pada background.
4. Mempersiapkan database service pada container dan menyimpan data pada mesin host (multi container apps)
Untuk mendefinisikan database service pada container dan volume untuk menyimpan data pada host, kita harus menyesuaikan file compose.yaml
sebagai berikut.
services:
server:
build:
context: .
environment:
NODE_ENV: production
POSTGRES_HOST: db
POSTGRES_USER: postgres
POSTGRES_PASSWORD_FILE: /run/secrets/db-password
POSTGRES_DB: example
ports:
- 3000:3000
depends_on:
db:
condition: service_healthy
db:
image: postgres
restart: always
user: postgres
secrets:
- db-password
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=example
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
expose:
- 5432
healthcheck:
test: [ "CMD", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
Pada compose.yaml
yang telah dimodifikasi, kita mendefinisikan db
service. Pada db
service, kita mendefinisikan image
yang digunakan oleh service tersebut, perilaku yang ditetapkan ketika container mengalami termination (selalu restart container), secrets
yang mendefinisikan data sensitif, volumes
yang mendefinisikan folder pada mesin host yang dikelola oleh docker untuk mempertahankan data, environment
untuk mendefinisikan environment variable, dan expose
untuk mendefinisikan port yang Compose ekspos pada suatu container untuk suatu service.
Selain itu, perhatikan bahwa db
service memiliki healthcheck
yang berfungsi untuk memeriksa apakah service db
“sehat” atau tidak. Hal ini bertujuan untuk mengecek apakah database PostgreSQL berhasil dijalankan dengan baik dan siap untuk menerima koneksi baru. Kemudian, perhatikan bahwa service server
bergantung pada service db
dengan condition: service_healthy
. Hal ini untuk memastikan db
service diharapkan dalam kondisi baik dan siap menerima koneksi baru sebelum memulai server
service yang akan membutuhkan db
tersebut dan membuat koneksi dengannya.
Kemudian, karena db-password
dari secrets
mengakses file db/password.txt
, kita harus menyiapkannya pada direktori saat ini. File password.txt
akan berisi password dari akun pada PostgreSQL.
Kemudian, kita dapat menjalankan aplikasi dengan docker compose up --build
.
Jika kita menjalankan docker volume list
, kita bisa mengecek ada docker-nodejs-sample_db-data
yang baru terbuat. Pada dasarnya volume adalah folder pada mesin host yang dikelola langsung oleh Docker. Dengan demikian, data pada volume akan bertahan selama volume tersebut tidak dihapus pada mesin host. Selain itu, kita dapat mencari folder tersebut pada mesin host seperti pada contoh di bawah untuk volume db-data
.
Dengan demikian, apabila kita mencoba membuat todo pada http://localhost:3000, kemudian menghapus container dengan docker compose rm
dan menjalankan aplikasi kembali, data todo tetap tersedia.
5. Menjalankan Test pada Docker Container dengan Multi-stage Dockerfile
Pada bagian ini, kita akan melihat cara untuk menjalankan unit tests pada Docker. Perhatikan bahwa aplikasi docker-nodejs-sample
sudah memiliki package Jest untuk menjalankan tes dan memiliki tes di dalam direktori spec
. Berikut adalah perubahan pada Dockerfile
.
ARG NODE_VERSION=20.11.1
FROM node:${NODE_VERSION}-alpine as base
WORKDIR /usr/src/app
EXPOSE 3000
FROM base as prod
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --omit=dev
USER node
COPY . .
CMD node src/index.js
FROM base as test
ENV NODE_ENV test
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --include=dev
USER node
COPY . .
RUN npm run test
Pada Dockerfile
di atas, kita mendefinisikan terlebih dahulu stage base
sebagai stage dasar. Kemudian, kita akan merujuk stage ini pada stage prod
yang akan digunakan untuk membangun image untuk production dan pada stage test
yang akan digunakan untuk membangun image untuk testing. Kemudian, untuk membuat docker image dengan menggunakan stage test
kita harus menspesifikkan opsi --target
ketika menjalankan docker build -t <nama_image> --target <nama_target> .
dan untuk memastikan testing selalu berjalan, kita harus menspesifikkan opsi ---no-cache
. Selain itu, kita bisa menggunakan opsi --progress=plain
untuk melihat output dari build yang mencakup hasil dari unit test yang dijalankan dari perintah RUN npm run test
. Berikut adalah contoh eksekusi.
Melalui hal ini, kita dapat menjalankan unit test terlebih dahulu ketika ingin membangun sebuah image. Selain itu perhatikan bahwa Instruksi RUN
akan berjalan ketika image sedang di-build dan proses build akan gagal jika pengujian gagal.
6. Konfigurasi CI/CD Pipeline
Pada tahap ini, kita akan mempersiapkan dan menggunakan Github Actions untuk build, test, dan push image dari aplikasi docker-nodejs-sample
ke Docker Hub. Namun, sebelum itu, kita harus mempersiapkan dan mengonfigurasikan Docker Hub personal access token (PAT) ke GitHub repository terlebih dahulu. Berikut adalah tahapan yang perlu kalian lakukan.
- Akses Personal Access Token pada Docker Hub
Secara sederhana, Docker Hub adalah registry yang digunakan untuk menyimpan, mencari, menggunakan, dan berbagi berbagai container image. Oleh karena itu, sign up atau sign in dengan akun Anda dan dapatkan akses token. Akses My Account terlebih dahulu dan pilih menu Security seperti berikut.
Kemudian, buat access token baru dengan mengeklik New Access Token. Kita dapat memberikan nama node-docker
pada PAT yang baru saja kita buat. Selain itu, perhatikan bahwa permission access untuk PAT tersebut berupa read dan write. Permission access ini digunakan untuk menentukan cakupan dan batasan terhadap repository di Docker. Dengan permission access read dan write dari PAT, CI/CD pipeline seperti GitHub Actions dapat membuat image dan kemudian mem-push image tersebut ke repositori. Namun, Github Actions tidak dapat menghapus repositori tersebut.
Setelah itu, kita akan ditampilkan access token sekali. Jangan lupa untuk mencatat nilai dari access token yang telah dihasilkan.
2. Konfigurasi Secrets pada GitHub Actions
Karena PAT merupakan informasi sensitif seperti halnya sebuah password, kita tidak ingin mengekspos PAT tersebut ke publik. Dengan demikian, agar GitHub Actions tetap dapat mengakses PAT tersebut untuk mem-push image ke repository docker milik Anda, kita harus menambahkan secret pada GitHub repository milik kita.
Akses halaman repository yang telah kalian fork sebelumnya, kemudian akses menu Settings. Pada Settings, akses Secrets and variables > Actions.
Kemudian, kita tambahkan 2 secrets sebagai berikut. DOCKER_USERNAME
berisi nama dari akun docker kalian dan DOCKERHUB_TOKEN
berisi PAT kalian.
3. GitHub Actions Workflow
Secara sederhana, GitHub Actions Workflow adalah alur kerja otomatis yang dapat dikonfigurasi untuk menjalankan satu atau beberapa job, seperti untuk building, testing, dan deployment. Pada tahap ini, kita akan menggunakan GitHub Actions Workflow untuk building, testing, dan pushing image ke repository pada Docker Hub.
Untuk membuat Github Actions Workflow, buat file main.yml
dengan konten sebagai berikut pada folder .github/workflows
pada repository kalian.
name: ci
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build and test
uses: docker/build-push-action@v5
with:
context: .
target: test
load: true
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64/v8
push: true
target: prod
tags: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
Pada file main.yml
, kita mendefinisikan workflow bernama ci
yang berguna untuk mengotomasi proses building, testing, dan pushing image ke repository pada Docker Hub ketika terdapat push commit ke branch main
. Pada workflow ini, kita mendefinisikan 1 buah jobs, yaitu build, dengan beberapa step sebagai berikut.
- Job build ini akan berjalan pada virtual machine dengan sistem operasi terbaru dari Ubuntu.
- Step
Checkout
berfungsi untuk memungkinkan workflow kita mengakses repository kita. - Step
Login to Docker Hub
untuk mengautentikasi dengan Docker Hub menggunakan kredensial yang disimpan di GitHub Secrets (DOCKER_USERNAME
danDOCKERHUB_TOKEN
). - Step
Set up Docker Buildx
menyiapkan Docker Buildx, sebuah plugin CLI Docker yang meng-extend kemampuan build Docker dengan fitur-fitur seperti build multi-platform. - Step Build and test untuk mem-build image dari current directory sebagai build context (
.
). Selain itu, kita menspesifikkan build stage yang digunakan yaitu stagetest
padaDockerfile
yang telah dibuat sebelumnya. Selain itu, opsiload
digunakan untuk menyimpan image pada Docker Daemon pada virtual machine. - Step Build and push untuk mem-build image untuk beberapa platform (
linux/amd64
danlinux/arm64/v8
) dari current directory sebagai build context (.
) dengan build stageprod
. Selain itu, step ini juga akan mem-push image yang dibuat ke Docker Hub dengan nama image yang dispesifikkan padatags
.
4. Jalankan GitHub Actions Workflow
Kemudian, untuk mencoba menjalankan workflow yang telah dibuat pada VS Code, kita cukup mempush commit terbaru yang berisi workflow main.yml
ke GitHub repository (remote repository).
Pada menu Actions di GitHub, kita dapat melihat informasi mengenai workflow yang sedang berjalan.
Selain itu, bila kita mendownload ekstensi Github Actions pada VS Code, kita juga dapat melihat informasi tersebut langsung pada VS Code seperti berikut.
Hasil dari eksekusi workflow adalah image yang tersedia pada repository docker-nodejs-sample
pada Docker Hub seperti berikut.
Lalu, apakah hal ini sebenarnya dapat dijalankan secara manual? Ya! Kalian dapat menggunakan Docker CLI pada perangkat kalian untuk melakukan hal yang telah dilakukan oleh workflow. Namun, jika kita melakukan ini secara manual, hal ini berarti kita harus mengulangin hal ini setiap kali kita melakukan perubahan pada kode repository. Sebagai programmer, tentu saja kita ingin mengotomasi hal-hal seperti ini.
7. Test deployment ke Kubernetes
Sebelum kita melakukan deployment ke Google Cloud Platform, ada baiknya bagi kita untuk menguji deployment secara lokal dengan menguji dan men-debug workload pada Kubernetes. Berikut adalah tahapannya.
- Aktifkan Kubernetes pada Docker Desktop
Untuk mengaktifkan Kubernetes, kita dapat mengakses settings pada Docker Desktop, centang tombol Enable Kubernetes, dan klik Apply and restart.
2. Persiapkan Konfigurasi untuk Kubernetes
Pada git repository, buat file docker-node-kubernetes.yaml
dengan isi sebagai berikut. Jangan lupa untuk mengganti DOCKER_USERNAME
dan REPO_NAME
dengan nilai yang sesuai. DOCKER_USERNAME
berupa nama dari akun Docker Hub kalian dan REPO_NAME
berupa nama repository docker pada Docker Hub yang telah kalian buat sebelumnya melalui Github Actions.
apiVersion: apps/v1
kind: Deployment
metadata:
name: docker-nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
todo: web
template:
metadata:
labels:
todo: web
spec:
containers:
- name: todo-site
image: DOCKER_USERNAME/REPO_NAME
imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: todo-entrypoint
namespace: default
spec:
type: NodePort
selector:
todo: web
ports:
- port: 3000
targetPort: 3000
nodePort: 30001
Pada file di atas, terdapat 2 komponen: Deployment
dan Service
. Deployment
berfungsi untuk mendefinisikan pods dan container yang berada pada pods tersebut. Secara sederhana, pods adalah basic unit untuk deployment pada kubernetes yang bisa mengandung lebih dari satu container pada suatu pods tertentu. Service
yang akan mengarahkan traffic dari port 30001 pada mesin host ke port 3000 di dalam pod sedemikian hingga kita dapat mengakses aplikasi kita.
3. Deploy aplikasi ke Kubernetes
Untuk mendeploy aplikasi ke Kubernetes, kita dapat menjalankan perintah kubectl apply -f docker-node-kubernetes.yaml
.
Untuk mengecek deployment, kita dapat menjalankan perintah kubectl get deployments
untuk mengecek komponen Deployment
dan perintah kubectl get services
untuk mengecek komponen Service
.
Kemudian, kita bisa mengakses aplikasi dengan mengunjungi URL localhost:30001 pada browser kita seperti berikut.
Terakhir, untuk menghapus aplikasi yang telah dideploy pada kubernetes, kita dapat menjalankan perintah kubectl delete -f docker-node-kubernetes.yaml
.
8. Deploy ke Google Cloud Run dari Google Cloud Platform
Pada tahap ini, kita akan men-deploy aplikasi dengan memanfaatkan Google Cloud Run yang tersedia pada Google Cloud Platform. Google Cloud Run adalah platform komputasi serverless yang memungkinkan kita untuk menjalankan container di atas infrastruktur Google yang skalabel.
Serverless di sini berarti kita tidak perlu mengatur atau mengelola infrastruktur server secara langsung. Google Cloud Run akan menangani penyediaan dan pen-skalaan dari container aplikasi kita secara otomatis, berdasarkan permintaan yang masuk. Ini memungkinkan kita untuk fokus pada pengembangan aplikasi tanpa harus khawatir tentang konfigurasi server dan skalabilitasnya.
Tentu saja, GCP tidak gratis, tetapi Anda dapat memperoleh kredit gratis pada saat pendaftaraan untuk pertama kali. Oleh karena itu, buat proyek GCP terlebih dahulu dan kredit yang diperlukan untuk GCP.
Berikut adalah tahapan yang perlu dilakukan.
- Buat Service Account Key JSON pada GCP.
Service Account Key JSON adalah kredensial yang akan digunakan oleh GitHub Actions untuk melakukan autentikasi ke Google Cloud. Oleh karena itu, kita harus membuatnya terlebih dahulu dan menyimpannya sebagai secrets pada GitHub repository.
Kemudian, klik Create Service Account untuk membuat Service Account Key JSON. Isi informasi yang dibutuhkan hingga Done. Selain itu, kita harus memberikan access kepada service account ke proyek GCP kita. Pada contoh di bawah, saya memberikan akses Owner kepada service account yang dibuat. Secara sederhana, role Owner memungkinkan service account untuk melakukan berbagai aksi terhadap resources yang ada pada proyek GCP kita.
Berikut adalah service account yang telah dibuat.
Klik ⋮ pada kolom Actions, lalu klik Manage keys.
Klik Create new key dengan format JSON.
Setelah mengeklik tombol Create, file JSON akan terunduh pada perangkat kalian. File JSON akan mengadung informasi mengenai private key dan informasi lainnya. Perhatikan bahwa kita harus menyimpan file ini dengan hati-hati.
Setelah kita berhasil membuat Service Account Key JSON di GCP, buat secrets pada GitHub repository dengan value berupa Service Account Key JSON tersebut. Selain itu, kita disarankan untuk mengecilkan JSON menjadi satu baris string sebelum menyimpannya dalam GitHub secrets.
2. Buat Job Deployment pada GitHub Actions Workflow
name: ci
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build and test
uses: docker/build-push-action@v5
with:
context: .
target: test
load: true
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64/v8
push: true
target: prod
tags: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
deployment:
runs-on: ubuntu-latest
needs: build
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
-
name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v2
with:
service: 'docker-nodejs-sample'
image: docker.io/${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
flags: '--port=3000'
region: 'us-central1'
Pada file di atas, kita menambahkan job baru, yaitu deployment
yang akan dijalankan pada virtual machine dengan sistem operasi Ubuntu. Perhatikan bahwa job deployment
bergantung dengan job build
. Hal ini berarti job deployment
tidak akan dijalankan apabila job build
tidak berhasil dijalankan terlebih dahulu. Pada job tersebut, terdapat 3 step yang akan dilakukan sebagai berikut.
- Step
Checkout
berfungsi untuk memungkinkan workflow kita mengakses repository kita. Hal ini merupakan prasyarat untuk menggunakan actiongoogle-github-actions/auth@v2
. - Step
Authenticate to Google Cloud
berfungsi untuk melakukan autentikasi ke Google Cloud sedemikian hingga workflow dapat mengakses berbagai fitur dari Google Cloud. - Step
Deploy to Cloud Run
berfungsi untuk melakukan deployment aplikasi dalam bentuk docker image ke Google Cloud. Container dari docker image tersebut akan dijalankan oleh Cloud Run sesuai dengan kebutuhan (ingat bahwa Cloud Run bersifat serverless).
Setelah kita melakukan deployment aplikasi ke Google Cloud Run, kita dapat mengakses aplikasi tersebut melalui URL yang tersedia pada detail service kita.
Berikut adalah contoh mengakses aplikasi tersebut berdasarkan URL di atas.
Selamat! Kita berhasil mendeploy aplikasi kita dengan docker ke Google Cloud Run. Aplikasi kita siap diakses secara publik oleh Anda dan pengguna aplikasi Anda!
Kelebihan dan Kelemahan Deployment dengan Docker
Berikut adalah penjelasan singkat mengenai kelemahan dan kelebihan dari Docker.
Pro:
- Portabilitas: container dapat berjalan di sistem apa pun yang mendukung Docker. Hal ini membuat aplikasi menjadi portabel di berbagai lingkungan.
- Isolasi: container menyediakan tingkat isolasi yang tinggi untuk aplikasi (container tidak saling mengganggu satu sama lain) dan konsisten di berbagai lingkungan.
- Efisiensi: container Docker ringan dan menggunakan sumber daya secara efisien. Bahkan, container Docker tidak terlalu banyak menggunakan sumber daya dibandingkan dengan virtual machine. Hal ini memungkinkan penerapan dan penskalaan aplikasi yang lebih cepat.
- Manajemen dependencies: Docker menyederhanakan manajemen dependecies dengan membungkus semua dependecies dengan aplikasi sehingga mengurangi masalah kompatibilitas.
- Integrasi DevOps: Docker memfasilitasi praktik DevOps dengan memanfaatkan CI/CD pipeline.
Cons:
- Kurva pembelajaran: Untuk menguasai Docker, kita akan memerlukan waktu cukup banyak untuk belajar mengenai penggunaan Docker dan berbagai tools untuk mengelola Docker container dengan baik, seperti Kubernetes.
- Orkestrasi yang terbatas: Tanpa orkestrasi yang ekstensif, akan sulit untuk mengelola beberapa kontainer dan lingkungan secara bersamaan. Hal ini berarti Docker akan membutuhkan alat bantu seperti Kubernetes untuk mengelola berbagai container yang ada. Tentunya, Kubernetes juga memiliki kurva pembelajaran yang tinggi.
Studi Kasus Docker untuk Next.js: Questify
Pada contoh kasus sebelumnya, kita telah membungkus aplikasi node.js sederhana ke dalam suatu container. Namun, bagaimana bila kita ingin membungkus aplikasi dengan framework tertentu seperti Next.js yang merupakan framework untuk frontend dengan Docker? Tentu saja bisa! Namun, kita harus menyesuaikan DockerFile
sedemikian hingga aplikasi Next.js kita dapat berjalan dengan baik. Berikut adalah contoh aplikasi Next.js yang akan dibungkus ke dalam container dengan Docker.
Lalu, bagaimana cara membuat DockerFile
yang sesuai? Tentu saja, kita cukup menulis perintah yang sesuai sedemikian hingga aplikasi Next.js kita dapat berjalan dengan baik. Pada contoh di bawah, kita akan menerapkan multi-stage build.
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD HOSTNAME="0.0.0.0" node server.js
# Testing
FROM base AS test
ENV NODE_ENV test
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --include=dev
COPY . .
RUN npm run test
Pada DockerFile
di atas, terdapat beberapa stage sebagai berikut.
- Stage
base
: Mendefinisikan image dasar yang digunakan, yaitunode:18-alpine
. - Stage
deps
: Menginstal semua dependensi yang dibutuhkan oleh aplikasi Next.js. - Stage
builder
: Membangun aplikasi Next.js. Perhatikan bahwanpm run build
akan menghasilkan file-file yang dibutuhkan untuk menjalankan aplikasi Next.js. - Stage
runner
: Menyiapkan lingkungan produksi untuk menjalankan aplikasi. - Stage
test
: Menyiapkan lingkungan pengembangan untuk menguji aplikasi.
Dengan DockerFile
seperti di atas, kita dapat menjalankan perintah berikut.
docker build -t questify-fe --progress=plain --no-cache --target test .
: Melakukan pengujian pada Docker container yang dibangun.docker build -t questify-fe --target runner .
: Membangun Docker Image untuk production.docker run -p 3000:3000 -d questify-fe
: Menjalankan Docker container dari imagequestify-fe
.
Sumber Referensi
- https://www.theodinproject.com/lessons/nodejs-deployment
- https://docs.docker.com/language/nodejs/
- https://www.knowledgenile.com/blogs/pros-cons-implementing-paas
- https://duplocloud.com/blog/docker-advantages-and-disadvantages/
- https://cloud.google.com/blog/products/devops-sre/deploy-to-cloud-run-with-github-actions/
- https://cloud.google.com/iam/docs/keys-list-get
- https://medium.com/pujanggateknologi/serverless-kubernetes-pada-google-cloud-run-36c1fce30380
- https://medium.com/@itsuki.enjoylife/dockerize-a-next-js-app-4b03021e084d