Tingkatkan Kemampuan Deployment dengan Docker

LyzanderAndrylie
22 min readMay 19, 2024

--

Docker Logo by 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.

  1. Deployment dengan platform as a service (PaaS) sebagai cara yang paling mudah untuk memulai deployment.
  2. Kelebihan dan kelemahan deployment dengan PaaS.
  3. Deployment dengan Docker.
  4. Kelebihan dan kelemahan deployment dengan Docker.
  5. 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.

Contoh proses deployment oleh Adaptable.io

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.

Hasil Fork Repository Git docker-nodejs-sample

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.

What Is Docker by HowToDocker

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.

docker init pada docker-nodejs-sample

Berikut adalah penjelasan untuk masing-masing file.

  1. .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.
  2. Dockerfile: file yang berisi instruksi untuk membangun image.
  3. compose.yaml: file yang digunakan untuk mengonfigurasikan layanan, jaringan, volume, dll.
  4. 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 instruksi RUN, CMD, ENTRYPOINT, COPY, dan ADD yang mengikutinya di dalam Dockerfile.
  • 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 dan WORKDIR.
  • 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 oleh Dockerfile.

Setelah kalian mengetahui hal tersebut, kalian dapat mencoba untuk membuat image dan menjalankan container dari perintah berikut.

  1. 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.

docker build -t docker-nodejs-sample-image .

Untuk melihat daftar image yang ada, kita dapat menjalankan docker image ls.

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.

docker run -p 3000:3000 docker-nodejs-sample-image
Todo App dari docker-nodejs-sample

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.

run and stop docker container which run in background

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.

docker compose up --build
docker container yang sedang aktif

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.

password.txt pada docker-nodejs-sample

Kemudian, kita dapat menjalankan aplikasi dengan docker compose up --build.

docker compose up --build
docker volume dan container list

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.

Todo Test yang tetap tersedia walaupun kita menghapus container dan

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.

  1. Akses Personal Access Token pada Docker Hub
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.

Menu My Account
Menu Security pada Account Settings

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.

New Access Token Modal

Setelah itu, kita akan ditampilkan access token sekali. Jangan lupa untuk mencatat nilai dari access token yang telah dihasilkan.

Access Token pada Docker
Access Token pada Docker

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.

Actions secrets and variables pada GitHub repository

Kemudian, kita tambahkan 2 secrets sebagai berikut. DOCKER_USERNAME berisi nama dari akun docker kalian dan DOCKERHUB_TOKEN berisi PAT kalian.

Actions secrets and variables pada GitHub repository

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.

  1. Job build ini akan berjalan pada virtual machine dengan sistem operasi terbaru dari Ubuntu.
  2. Step Checkout berfungsi untuk memungkinkan workflow kita mengakses repository kita.
  3. Step Login to Docker Hub untuk mengautentikasi dengan Docker Hub menggunakan kredensial yang disimpan di GitHub Secrets (DOCKER_USERNAME dan DOCKERHUB_TOKEN).
  4. 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.
  5. Step Build and test untuk mem-build image dari current directory sebagai build context (.). Selain itu, kita menspesifikkan build stage yang digunakan yaitu stage test pada Dockerfile yang telah dibuat sebelumnya. Selain itu, opsi load digunakan untuk menyimpan image pada Docker Daemon pada virtual machine.
  6. Step Build and push untuk mem-build image untuk beberapa platform (linux/amd64 dan linux/arm64/v8) dari current directory sebagai build context (.) dengan build stage prod. Selain itu, step ini juga akan mem-push image yang dibuat ke Docker Hub dengan nama image yang dispesifikkan pada tags.

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).

Git push ke GitHub Repository

Pada menu Actions di GitHub, kita dapat melihat informasi mengenai workflow yang sedang berjalan.

Actions pada GitHub

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.

docker-nodejs-sample pada Docker Repositor Hub

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.

  1. Aktifkan Kubernetes pada Docker Desktop

Untuk mengaktifkan Kubernetes, kita dapat mengakses settings pada Docker Desktop, centang tombol Enable Kubernetes, dan klik Apply and restart.

Kubernetes Pada Docker Desktop

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.

Deploy aplikasi pada Kubernetes

Untuk mengecek deployment, kita dapat menjalankan perintah kubectl get deployments untuk mengecek komponen Deployment dan perintah kubectl get services untuk mengecek komponen Service.

Deployment dan Service pada Kubernetes

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.

Menghapus Aplikasi yang telah dideploy

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.

  1. 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.

Service Accounts pada Google Cloud Platform

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.

Create service account pada Google Cloud

Berikut adalah service account yang telah dibuat.

Service Accounts pada Google Cloud

Klik ⋮ pada kolom Actions, lalu klik Manage keys.

Manage Keys pada Google Cloud

Klik Create new key dengan format JSON.

Create New Key pada suatu Service Account
Buat Private Key dari Service Account

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.

Join Lines pada VS Code
Secrets pada GitHub Repository

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.

  1. Step Checkout berfungsi untuk memungkinkan workflow kita mengakses repository kita. Hal ini merupakan prasyarat untuk menggunakan action google-github-actions/auth@v2.
  2. Step Authenticate to Google Cloud berfungsi untuk melakukan autentikasi ke Google Cloud sedemikian hingga workflow dapat mengakses berbagai fitur dari Google Cloud.
  3. 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).
GitHub Workflow untuk Deployment
Cloud Run pada Google Cloud

Setelah kita melakukan deployment aplikasi ke Google Cloud Run, kita dapat mengakses aplikasi tersebut melalui URL yang tersedia pada detail service kita.

docker-nodejs-sample Service pada Google Cloud Run

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.

Frontend Aplikasi Questify

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.

  1. Stage base: Mendefinisikan image dasar yang digunakan, yaitu node:18-alpine.
  2. Stage deps: Menginstal semua dependensi yang dibutuhkan oleh aplikasi Next.js.
  3. Stage builder: Membangun aplikasi Next.js. Perhatikan bahwa npm run build akan menghasilkan file-file yang dibutuhkan untuk menjalankan aplikasi Next.js.
  4. Stage runner: Menyiapkan lingkungan produksi untuk menjalankan aplikasi.
  5. Stage test: Menyiapkan lingkungan pengembangan untuk menguji aplikasi.
Aplikasi Next.js Questify yang telah berhasil dijalankan

Dengan DockerFile seperti di atas, kita dapat menjalankan perintah berikut.

  1. docker build -t questify-fe --progress=plain --no-cache --target test .: Melakukan pengujian pada Docker container yang dibangun.
  2. docker build -t questify-fe --target runner .: Membangun Docker Image untuk production.
  3. docker run -p 3000:3000 -d questify-fe: Menjalankan Docker container dari image questify-fe.

Sumber Referensi

  1. https://www.theodinproject.com/lessons/nodejs-deployment
  2. https://docs.docker.com/language/nodejs/
  3. https://www.knowledgenile.com/blogs/pros-cons-implementing-paas
  4. https://duplocloud.com/blog/docker-advantages-and-disadvantages/
  5. https://cloud.google.com/blog/products/devops-sre/deploy-to-cloud-run-with-github-actions/
  6. https://cloud.google.com/iam/docs/keys-list-get
  7. https://medium.com/pujanggateknologi/serverless-kubernetes-pada-google-cloud-run-36c1fce30380
  8. https://medium.com/@itsuki.enjoylife/dockerize-a-next-js-app-4b03021e084d

--

--