Aku Dah Outgrow Laravel Valet
Published:
Updated:
Aku suka development environment yang simple.
Kalau boleh, aku nak buka laptop, git clone, setup sikit, terus boleh start coding.
Tak nak fikir pasal Kubernetes.
Tak nak fikir pasal container networking.
Tak nak fikir pasal SSL certificate.
Tak nak fikir pasal reverse proxy.
Just code.
Sebab tu untuk beberapa tahun kebelakangan ni, Laravel Valet memang ngam dengan workflow aku.
Mac + Homebrew + Valet.
Simple.
Tapi kebelakangan ni aku perasan satu benda.
Aku rasa aku dah outgrow setup tu.
Bukan sebab Valet tak bagus.
Actually, Valet masih sangat bagus.
Masalahnya adalah project yang aku buat sekarang dah mula perlukan benda-benda yang tak boleh sekadar brew install dan terus jalan.
Dan bila itu berlaku, development environment yang selama ni "just works" mula jadi semakin complicated.
1. Setup yang "just works"
Setup asal aku sebenarnya sangat simple.
Mac aku ada:
Mac
│
├── PHP
├── MySQL
├── Redis
├── Node
├── Composer
│
└── Laravel Valet
Laravel project duduk dalam folder tertentu.
Valet serve project tu.
Aku boleh access:
https://project.test
SSL pun jalan.
Tak perlu setup certificate sendiri.
Tak perlu setup Nginx sendiri.
Tak perlu create Docker network.
Tak perlu fikir port.
Dan yang paling best:
browser tak complain apa-apa.
HTTPS tu trusted.
Aku boleh buka Chrome dan terus kerja.
Untuk development Laravel biasa, memang susah nak beat benda ni dari segi simplicity.
Sebab tu aku memang suka native development.
Kalau benda boleh jalan native dengan mudah, aku tak nampak sebab untuk containerize semuanya.
MySQL?
Install native.
PHP?
Install native.
Redis?
Boleh native.
Node?
Native/NVM.
Laravel?
Valet.
Done.
Masalahnya...
Project tak semestinya kekal simple.
2. Sampai satu tahap, project mula demand benda lain
Bila bina software, requirement tak semestinya berhenti pada:
Laravel + MySQL.
Kadang-kadang kita perlukan sesuatu yang specific.
Contohnya ada satu library yang memang dah solve masalah yang kita nak solve.
Tapi library tu bukan PHP library.
Dia mungkin perlukan runtime lain.
Environment lain.
Language lain.
Atau memang developer library tu dah sediakan Docker image sebab itu cara paling senang untuk run benda tersebut.
Dalam keadaan macam ni, aku ada dua pilihan.
Pertama:
Aku cuba recreate benda tu sendiri dalam environment aku.
Kedua:
Aku guna existing solution dan adjust architecture aku supaya boleh accommodate benda tu.
Aku biasanya pilih nombor dua.
Sebab aku tak nampak value untuk spend beberapa hari recreate benda yang dah wujud.
Contoh yang aku jumpa ialah Carbone.
Carbone solve satu masalah yang aku perlukan.
Tapi dia bukan sekadar:
composer require something
dan settle.
Dia ada environment dia sendiri.
Jadi Docker mula nampak sangat masuk akal.
Dan dekat sinilah workflow aku mula berubah.
3. Aku tak terus Dockerize semuanya
Ini part yang aku rasa ramai developer akan buat secara berbeza.
Bila dah nampak Docker, temptation dia ialah:
"Okay. Kalau macam tu, Dockerize semua."
Laravel masuk Docker.
MySQL masuk Docker.
Redis masuk Docker.
Node masuk Docker.
Nginx masuk Docker.
Semua masuk Docker.
Lepas tu development environment jadi satu docker-compose.yml yang panjangnya macam assignment universiti.
Aku tak nak macam tu.
Kalau sesuatu benda dah berjalan dengan baik secara native, aku tak nampak sebab untuk pindahkan dia semata-mata sebab kita dah guna Docker untuk benda lain.
Jadi aku prefer hybrid.
Contohnya:
Mac
│
├── Laravel
│
├── MySQL
│
├── Valet
│
└── Docker
├── Redis
└── Carbone
Atau untuk project tertentu:
Mac
│
├── MySQL
│
└── Docker
├── Laravel
├── Redis
└── Carbone
Basically:
Docker hanya untuk benda yang memang perlukan Docker.
MySQL tak perlu masuk Docker kalau MySQL native aku dah elok.
Kenapa nak tambah satu lagi layer untuk benda yang tak ada masalah?
Ini bagi aku satu principle yang penting:
Jangan tambah complexity pada benda yang tak perlukan complexity.
4. Tapi bila Docker masuk, SSL pula jadi masalah
Ini part yang buat aku mula rasa:
"Okay... development setup aku dah tak simple."
Sebab bila semua native dan guna Valet, HTTPS memang senang.
Aku ada:
https://project.test
dan browser trusted.
Tapi sekarang aku ada service yang running dalam Docker.
Aku nak juga access melalui:
https://project.test
bukan:
http://localhost:8080
Sebab dalam real application, HTTPS bukan sekadar cosmetic.
Ada application yang behaviour dia bergantung pada HTTPS.
Ada OAuth callback.
Ada secure cookies.
Ada integration tertentu.
Dan honestly, aku memang dah biasa develop menggunakan HTTPS.
So aku cuba tengok Caddy.
Caddy memang menarik untuk benda macam ni.
Dia boleh handle HTTPS secara automatic dan boleh create local certificate authority.
Masalahnya bila Caddy running dalam Docker.
Conceptually kita ada:
Mac
│
├── Chrome
│
│ └── Trust Store
│
└── Docker
│
└── Caddy
│
└── Local CA
Caddy boleh generate certificate.
Tapi browser aku berada di luar container.
Browser tu tak semestinya trust CA yang Caddy generate dalam container.
Dan dekat sinilah aku baru nampak satu benda:
Generate certificate dengan trust certificate adalah dua benda berbeza.
Container boleh generate certificate.
Tapi browser tetap perlu tahu:
"Aku trust siapa?"
Kalau CA tu duduk dalam container, host tak automatically tahu pasal CA tersebut.
Jadi aku boleh dapat situation di mana:
HTTPS: ✅
Certificate: ✅
Caddy: ✅
Browser trust: ❌
Dan browser mula bagi warning.
Memang annoying.
5. Kalau aku sorang guna Mac, sebenarnya ada jalan yang cantik
Kalau semua orang dalam team guna Mac, aku boleh sebenarnya kekalkan Valet untuk bahagian SSL.
Docker expose application dekat local port.
Contohnya:
Docker
│
└── Application
│
└── localhost:8080
Kemudian Valet proxy:
Browser
│
│ HTTPS
▼
Valet
│
│ HTTP
▼
Docker
│
▼
Application
So SSL termination berlaku dekat host.
Docker cuma serve application.
Lebih kurang konsep:
valet proxy project http://127.0.0.1:3000
dan kemudian secure domain tu melalui Valet.
Ini actually nice.
Sebab Valet memang dah solve masalah local HTTPS untuk aku.
Tak perlu fight dengan Docker untuk benda yang Valet dah boleh buat.
Kalau aku seorang atau satu team yang semuanya guna Mac, aku mungkin akan stop dekat sini.
Valet + Docker hybrid.
Done.
Tapi kemudian aku teringat...
Aku bukan seorang.
6. Bila team masuk, solution yang cantik tadi mula tak cantik
Team boleh ada:
Mac
Linux
Windows
Dan sekarang aku tak boleh assume semua orang ada Valet.
Valet memang sangat nice kalau environment kau Mac.
Tapi dia bukan cross-platform development solution.
Jadi kalau workflow aku bergantung kepada:
"Install Valet, kemudian proxy Docker melalui Valet"
aku sebenarnya dah create another assumption.
Semua developer kena guna Mac.
Itu bukan assumption yang aku nak enforce.
Aku nak infrastructure yang lebih generic.
Docker sebenarnya dah bagus untuk part ni.
Docker boleh bagi semua orang environment service yang sama.
Tapi sekarang aku perlu solve HTTPS pula.
Dan bila aku cuba Caddy dalam Docker dekat Linux, aku jumpa issue yang sama.
Itu sebenarnya membantu aku faham root problem.
Masalahnya bukan Mac.
Bukan Fedora.
Bukan Windows.
Bukan Caddy.
Masalahnya adalah:
CA yang browser perlu trust berada di tempat yang salah.
7. Masuklah mkcert
Solution yang akhirnya lebih masuk akal untuk aku ialah mkcert.
Idea dia simple.
Daripada suruh Caddy dalam Docker generate CA sendiri...
Aku generate CA dekat host.
Host install CA tu sebagai trusted CA.
Kemudian aku generate certificate untuk domain local aku.
Contohnya:
mkcert -install
Kemudian:
mkcert project.test
Sekarang aku ada certificate untuk:
project.test
Dan yang lebih penting:
host aku trust certificate tersebut.
Lepas tu certificate tu aku mount masuk ke Docker.
So architecture dia jadi:
HOST
│
├── Browser
│
├── Trusted CA
│
└── project.test certificate
│
│ mount
▼
DOCKER
│
└── Caddy
│
└── Application
Ini bagi aku lebih clean.
Host handle trust.
Docker handle application.
Caddy handle HTTPS.
Setiap layer buat kerja masing-masing.
8. Caddy tak perlu create CA lagi
Dengan setup macam ni, Caddy tak perlu generate certificate sendiri.
Aku bagi dia certificate yang dah trusted oleh host.
Contohnya Caddyfile:
project.test {
tls /etc/caddy/certs/project.test.pem /etc/caddy/certs/project.test-key.pem
reverse_proxy app:8000
}
Dan Docker mount certificate:
services:
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./certs:/etc/caddy/certs
Sekarang flow dia:
Chrome
│
│ HTTPS
▼
Caddy
│
│ certificate
▼
Docker
│
▼
Laravel
Dan certificate tu datang daripada CA yang host memang dah trust.
So tak ada lagi:
"Eh kenapa browser merah?"
At least dari segi architecture, masalah trust tu dah diletakkan dekat tempat yang betul.
9. Ini yang aku sebenarnya belajar pasal SSL
Aku rasa selama ni aku tengok HTTPS terlalu simplistically.
Aku fikir:
"Ada certificate = HTTPS."
Actually tak.
Ada dua benda:
Server
└── ada certificate
Client
└── trust certificate issuer
Kalau server ada certificate tapi client tak trust issuer:
HTTPS works
but
browser complains
Bila semuanya native dengan Valet, kita tak terasa benda ni sebab Valet buat banyak kerja untuk kita.
Bila Docker masuk, boundary antara host dan container jadi jelas.
Dan tiba-tiba benda yang sebelum ni invisible jadi visible.
That's actually one of the interesting things about Docker.
Docker bukan semestinya buat benda lebih complicated.
Kadang-kadang Docker cuma expose complexity yang sebelum ni tooling lain sorokkan daripada kita.
10. Windows pula ada cerita lain
Cross-platform setup memang bagus.
Tapi jangan tipu diri sendiri.
Windows ada beberapa gotcha.
Contohnya WSL2.
Kalau browser aku running dekat Windows:
Windows
│
├── Chrome
│
└── WSL2
└── mkcert
Aku tak boleh simply assume:
"Aku install CA dalam WSL, jadi Chrome Windows akan trust."
WSL2 bukan Windows.
Dia environment yang berbeza.
Kalau browser aku dekat Windows, trust relationship tu perlu sampai ke Windows environment.
Sebab itu untuk Windows machine, aku lebih selesa kalau mkcert setup dibuat dalam Windows context yang sama dengan browser.
Dan ada satu lagi problem yang lagi susah:
Corporate machine.
Kalau company menggunakan Group Policy untuk control trusted root certificates, mungkin developer memang tak boleh sesuka hati install custom root CA.
Dalam situasi macam ni, tak kisah kau guna:
- mkcert
- Caddy
- Nginx
- Docker
- apa-apa pun
kalau company policy tak benarkan custom CA, memang kena ikut infrastructure yang company sediakan.
Ini benda yang selalu hilang dalam tutorial development.
Tutorial biasanya assume:
"Laptop kau adalah laptop kau. Install apa sahaja."
Real world tak semestinya macam tu.
11. Jadi sekarang aku patut Dockerize semuanya?
Tak.
At least bukan itu conclusion aku.
Aku masih suka native development.
Kalau project boleh jalan dengan:
Mac
├── PHP
├── MySQL
├── Redis
└── Valet
Aku akan guna benda tu.
Sebab dia simple.
Aku tak akan tambah Docker hanya sebab:
"Modern development kena Docker."
Tak.
Kalau tak ada masalah, jangan create masalah.
Tapi bila project perlukan sesuatu yang environment native aku tak boleh provide dengan mudah, Docker jadi tool yang sangat berguna.
Dan aku akan mula secara kecil.
Contohnya:
Native
├── Laravel
├── MySQL
└── Node
Docker
├── Carbone
└── Redis
Kalau lama-lama hybrid setup tu pula jadi terlalu complicated, barulah aku pertimbangkan untuk move lebih banyak benda ke Docker.
Bukan sebab Docker lebih cool.
Tapi sebab pada masa tu, Docker mungkin sudah menjadi less complicated option.
12. Development environment pun kena evolve
Aku rasa ini sebenarnya point paling besar daripada semua benda ni.
Kita selalu fikir architecture application akan evolve.
Mula-mula:
Laravel → MySQL
Kemudian:
Laravel
├── MySQL
├── Redis
└── Queue
Kemudian:
Laravel
├── MySQL
├── Redis
├── Queue
├── External service
└── Another runtime
Tapi kita jarang fikir benda yang sama berlaku dekat development environment.
Mula-mula:
Mac
└── Valet
Kemudian:
Mac
├── Valet
└── Docker
Kemudian mungkin:
Developer machine
├── Host
│ └── Trusted CA
│
└── Docker
├── Caddy
├── Laravel
├── Redis
├── Carbone
└── Other services
Dan itu normal.
Development environment bukan benda yang kita setup sekali kemudian tak sentuh lagi.
Dia pun kena evolve.
13. My rule sekarang
Kalau aku nak simplify semua benda ni, workflow aku lebih kurang macam ni.
Project simple?
Stay native.
Laravel + Valet + MySQL
Tak perlu Docker.
Ada satu dependency yang perlukan environment lain?
Dockerize dependency tersebut.
Jangan terus pindahkan semua benda.
Native Laravel
│
├── Native MySQL
│
└── Docker service
Hybrid setup dah mula jadi complicated?
Then consider Dockerize more of the stack.
Pada satu tahap, satu docker compose up mungkin lagi senang daripada maintain lima benda native.
Team guna OS yang berbeza?
Start thinking about standardization.
Mac boleh ada Valet.
Tapi jangan jadikan Valet sebagai dependency untuk semua orang kalau ada Windows dan Linux dalam team.
Local HTTPS?
Ingat satu benda:
Certificate boleh duduk dalam container. Trust kena sampai ke host/browser.
Untuk cross-platform local development, mkcert + mounted certificates dalam Docker bagi aku lebih masuk akal daripada cuba memaksa semua orang ikut satu OS-specific workflow.
Conclusion
Aku masih suka Laravel Valet.
Malah, kalau aku buat Laravel project yang straightforward, aku tak nampak sebab untuk buang Valet.
Dia cepat.
Dia simple.
Dia tak kacau kepala.
Tapi sekarang aku sedar satu benda:
"Just works" hanya valid untuk environment yang kita sedang gunakan.
Bila application evolve, requirement evolve.
Bila requirement evolve, infrastructure pun kena evolve.
Dan kadang-kadang kita akan sampai satu point di mana:
"Kenapa aku masih cuba paksa benda ni jalan macam dulu?"
Padahal masalahnya bukan code.
Masalahnya development environment kita dah tak match dengan complexity application.
Untuk aku, Docker bukan replacement kepada native development.
Docker adalah tool yang aku tarik masuk bila native environment dah tak cukup.
Dan aku tak perlu Dockerize everything.
Aku cuma perlu containerize benda yang memang patut di-containerize.
Sebab at the end of the day, tujuan development environment bukan untuk nampak sophisticated.
Tujuan dia adalah satu:
bagi aku boleh buka laptop dan terus buat kerja.
Kalau untuk capai benda tu aku perlukan Valet, guna Valet.
Kalau aku perlukan Docker, guna Docker.
Kalau aku perlukan kedua-duanya, guna kedua-duanya.
Yang penting bukan tool apa yang kita guna.
Yang penting setup tu tak jadi lebih menyusahkan daripada masalah yang dia sepatutnya solve.
