Berhenti Salin .env Manual: Sync Secrets Dev ke GitLab
Capek salin .env setiap ganti perangkat? Gue bikin script Python gratis yang sync secrets dev lewat GitLab CI/CD variables cukup satu command.

Waktu itu hampir tengah malam. Gue baru aja setup mesin development baru, clone project Next.js, install semua package. Semua udah siap. Terus gue ketemu tembok yang sama yang selalu gue ketemu.
File .env tidak ada.
Gue buka Slack dan nyari pesan lama waktu gue kirim variabel itu ke diri sendiri enam bulan lalu. Gue cek laptop lain. Gue gali-gali notes app dan dokumen cloud. Dua puluh menit kemudian akhirnya semua terkumpul dan app-nya jalan. Tapi waktu yang kebuang itu bikin gondok.
Ini bukan masalah sekali dua kali. Setiap ganti perangkat, setiap fresh clone project, gue ngelakuin ritual copy-paste yang sama. FastAPI, Laravel, Next.js. Stack-nya gak ngaruh. File .env selalu jadi bottleneck, dan gue selalu yang ngangkutnya secara manual.
Kenapa Solusi yang Udah Ada Gak Cocok Buat Gue
Gue udah research nih. Ada tools yang memang dibikin khusus buat masalah ini.
Dotenv Vault nawarin sync .env terenkripsi dengan CLI yang keren. Tapi fitur yang berguna ada di balik paket berbayar, dan itu nambahin satu lagi layanan eksternal yang harus gue percaya dan bergantung.
Infisical adalah secret manager open-source yang solid. Gue respek sama yang mereka bangun. Tapi setup Infisical self-hosted hanya untuk dev secrets pribadi rasanya kayak bawa truk pemadam kebakaran buat nyalahin lilin. Free tier cloud-nya ada batasnya, dan setup self-hosted-nya lumayan ribet.
Git Crypt mengenkripsi file langsung di repository menggunakan GPG keys. Konsepnya bersih, tapi butuh setup GPG di setiap mesin dan menanamkan secrets ke history repository dengan cara yang bikin gue kurang nyaman untuk repo yang di-share.
Chezmoi mengelola dotfiles lintas mesin dan ngelakuinnya dengan baik. Tapi itu satu lagi tools yang harus di-install, dikonfigurasi, dan dipelajari. Dan dia tidak ngerti per-project environment files dengan nilai berbeda per repo secara default.
Setiap opsi punya pola yang sama: biaya setup, entah uang, waktu, atau kompleksitas. Gue terus ketemu tembok itu.
Insight yang Mengubah Segalanya
Terus gue liat apa yang udah gue punya nih.
Hampir semua project gue tinggal di instance GitLab self-hosted. Host GitLab yang berbeda untuk klien dan tim yang berbeda, tapi tetap GitLab. Dan setiap project GitLab punya built-in CI/CD variables store yang bisa menyimpan string apapun, termasuk seluruh file .env yang disimpan sebagai satu variabel.
Gue udah bayar server-server itu. API-nya udah ada. Gue udah autentikasi ke instance-instance itu lewat Git. Infrastruktur yang gue butuhkan sudah ada. Gue cuma belum menggunakannya dengan cara ini.
Baca Juga: GitLab CI/CD: Variabel Dinamis Lintas Environment
Bikin env-sync
Gue nulis env-sync.py, script Python kecil dengan dua command. push mengupload file env lokal lo ke GitLab. pull mendownload balik ke direktori kerja lo. Tidak ada config file. Tidak ada layanan tambahan. Tidak ada subscription.
Ini script lengkapnya:
#!/usr/bin/env -S uv run --with requests
import os
import sys
import requests
import subprocess
VARIABLE_KEY = "DEV_ENV_FILE"
def get_git_info():
remote_url = subprocess.check_output(
["git", "config", "--get", "remote.origin.url"]
).decode().strip()
if remote_url.startswith("http"):
host = remote_url.split("/")[2]
path = "/".join(remote_url.split("/")[3:]).replace(".git", "")
else:
host = remote_url.split("@")[1].split(":")[0]
path = remote_url.split(":")[1].replace(".git", "")
return host, path.replace("/", "%2F")
def get_gitlab_token(host):
input_str = f"protocol=https\nhost={host}\n"
proc = subprocess.Popen(
["git", "credential", "fill"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True
)
stdout, _ = proc.communicate(input=input_str)
for line in stdout.splitlines():
if line.startswith("password="):
return line.split("=", 1)[1]
return None
def push_env(host, project_id, token):
env_files = [".env.local", ".env.dev", ".env.development", ".env"]
target_file = next((f for f in env_files if os.path.exists(f)), None)
if not target_file:
print("File env tidak ditemukan.")
return
with open(target_file, "r") as f:
content = f.read()
url = f"https://{host}/api/v4/projects/{project_id}/variables/{VARIABLE_KEY}"
headers = {"PRIVATE-TOKEN": token}
data = {"value": content, "variable_type": "file"}
res = requests.put(url, data=data, headers=headers)
if res.status_code == 404:
data["key"] = VARIABLE_KEY
res = requests.post(
f"https://{host}/api/v4/projects/{project_id}/variables",
data=data, headers=headers
)
if res.status_code in [200, 201]:
print(f"Push berhasil: {target_file} disimpan sebagai {VARIABLE_KEY}")
else:
print(f"Push gagal: {res.status_code} - {res.text}")
def pull_env(host, project_id, token):
url = f"https://{host}/api/v4/projects/{project_id}/variables/{VARIABLE_KEY}"
headers = {"PRIVATE-TOKEN": token}
res = requests.get(url, headers=headers)
if res.status_code == 200:
with open(".env", "w") as f:
f.write(res.json().get("value"))
print("Pull berhasil. Disimpan sebagai .env")
else:
print(f"Pull gagal. Variabel '{VARIABLE_KEY}' tidak ditemukan.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: env-sync [push|pull]")
sys.exit(1)
host, pid = get_git_info()
token = get_gitlab_token(host)
if not token:
print(f"Token untuk {host} tidak ditemukan. Jalankan 'git pull' sekali di repo ini dulu.")
sys.exit(1)
cmd = sys.argv[1].lower()
if cmd == "push":
push_env(host, pid, token)
elif cmd == "pull":
pull_env(host, pid, token)
else:
print("Command tidak dikenal. Gunakan 'push' atau 'pull'.")Cara Kerja Tiga Mekanisme Inti
Ada tiga keputusan desain dalam script ini yang bikin rasanya kayak invisible setelah di-setup.
Auto-detect host dan project GitLab
Waktu lo jalanin script di dalam direktori project, script baca remote URL git dengan git config --get remote.origin.url. Dia handle format SSH ([email protected]:org/project.git) dan format HTTPS (https://gitlab.example.com/org/project.git). Dari satu string itu dia extract host dan project path secara otomatis.
Inilah yang bikin satu script yang sama bisa jalan di semua instance GitLab lo tanpa config file atau environment variable tersendiri. Project-nya sendiri yang kasih tahu script harus kemana.
Reuse Git credentials yang udah ada
Daripada nyimpen GitLab Personal Access Token di suatu tempat, script ini memanggil git credential fill. Ini adalah credential helper yang sama yang Git sendiri pakai waktu lo autentikasi untuk git pull. Kalau lo udah autentikasi ke host GitLab itu lewat Git, script otomatis nemuin token dari OS credential store lo.
Di macOS itu Keychain. Di Linux itu credential helper yang lo konfigurasi, biasanya git-credential-store atau integrasi keyring desktop. Lo gak pernah perlu tempel token ke config file manapun.
Baca Juga: Hentikan Git Nanya Kredensial di VS Code
Simpan seluruh env sebagai satu CI/CD variable
Script menyimpan konten file env lo sebagai satu variabel bernama DEV_ENV_FILE dengan tipe diset ke file. Di push pertama, dia coba request PUT. Kalau GitLab balik 404 karena variabelnya belum ada, otomatis fallback ke POST untuk membuatnya. Lo gak perlu pre-create apapun di UI GitLab.
Waktu pull, script nulis nilai variabel balik ke file .env di direktori lo saat ini.
Setup Sekali Seumur Hidup
Semua ini adalah instalasi sekali jalan deh. Setelah selesai, lo gak perlu mikirin ini lagi.
Install uv
Script ini pakai uv sebagai runtime manager. Shebang #!/usr/bin/env -S uv run --with requests artinya uv otomatis install library requests sebelum jalan, jadi lo gak perlu manage virtual environment atau pip install apapun.
curl -LsSf https://astral.sh/uv/install.sh | shSimpan script dan jadikan executable
Simpan script sebagai ~/env-sync.py dan tandai sebagai executable:
chmod +x ~/env-sync.pyTambah alias global
Untuk macOS dengan zsh:
echo 'alias env-sync="~/env-sync.py"' >> ~/.zshrc
source ~/.zshrcUntuk Linux dengan bash:
echo 'alias env-sync="~/env-sync.py"' >> ~/.bashrc
source ~/.bashrcAutentikasi ke host GitLab lo sekali
Jalankan git pull di dalam project manapun di instance GitLab itu. Waktu Git minta credentials, masukkan username dan Personal Access Token lo. Git menyimpannya. Script akan pakai itu tanpa nanya lagi selamanya.
Kalau lo punya beberapa instance GitLab, ulangi sekali per host. Setiap host menyimpan credentials-nya secara terpisah.
Penggunaan Sehari-hari
Setelah semuanya siap, alurnya cuma satu command di salah satu arah aja nih.
Push env lokal lo saat ini ke GitLab dari dalam direktori project:
env-sync pushPull balik setelah clone di mesin baru:
env-sync pullTidak ada flag. Tidak ada project ID yang perlu diingat. Tidak ada token yang perlu disalin. Script figuring out everything dari lokasi lo berdiri.
Default Cerdas untuk Multi-Framework Projects
Beberapa detail yang bikin ini praktis lintas berbagai stack nih.
Untuk project Next.js yang pakai .env.local, script cek file itu dulu. Urutan prioritasnya adalah .env.local, terus .env.dev, terus .env.development, dan terakhir .env. Script pilih yang pertama kali ditemukan. Ini artinya Next.js, FastAPI, Laravel, dan framework lain manapun bisa jalan tanpa perlu ubah konfigurasi apapun.
Waktu pull, script selalu nulis ke .env. Ini disengaja. Kebanyakan framework pakai .env sebagai file base universal, jadi lo dapat output yang konsisten terlepas dari nama file sumbernya.
Yang Perlu Diperhatikan
Ini adalah tools untuk dev environment, bukan production secret manager. Beberapa limitasi yang jujur nih.
GitLab CI/CD variables bisa dilihat oleh maintainer dan owner project. Jangan simpan credentials production di sini ya. Ini hanya untuk nilai development lokal, yang tipenya udah di .gitignore dari awal.
Setiap developer mengelola DEV_ENV_FILE-nya sendiri per project. Variabelnya tidak otomatis di-share ke sesama anggota tim. Itu memang disengaja. File env dev sering berisi API key pribadi, port service lokal, dan setting spesifik mesin yang berbeda per developer.
Untuk secrets yang perlu di-share antar tim atau dipromosikan ke staging dan production, lo tetap butuh secret manager yang proper. Tools ini memecahkan masalah workflow developer pribadi, bukan masalah distribusi secrets tim.
Yang Gue Hemat
Dulu gue bisa habiskan antara lima sampai dua puluh menit untuk membuat project berjalan penuh setelah fresh clone, tergantung seberapa basi ingatan gue tentang variabel mana yang dibutuhkan. Sekarang waktu itu di bawah satu menit.
Script ini menggantikan Slack message archaeology, pencarian di notes app, dan copy-paste lintas perangkat dengan satu command aja. Tidak ada service baru yang perlu dioperasikan. Tidak ada subscription bulanan. Tidak ada GPG key yang perlu didistribusikan ke setiap mesin baru.
Ini berjalan identik di MacBook gue, workstation Linux gue, dan cloud development environment manapun yang gue spin up sementara. Satu-satunya prasyarat adalah mesin punya Git dan uv terinstall, yang memang sudah benar untuk semua mesin yang gue pakai develop.
Kalau lo udah pakai GitLab self-hosted, infrastruktur untuk ini udah ada di dalam project-project lo. Satu-satunya yang ditambahkan adalah satu script Python 120 baris dan satu baris di shell config lo.
Dapatkan Script-nya
Baca Juga: Menguasai Automated Docker Tagging di GitLab CI/CD: Panduan Praktis
Simpan script di ~/env-sync.py. Setelah alias ditambahkan, setiap project Git di GitLab instance manapun bisa push dan pull .env-nya dengan satu command. Setup pertama kali butuh kurang dari sepuluh menit. Setiap penggunaan berikutnya butuh kurang dari dua detik.
Kadang solusi terbaik adalah yang dibangun dari tools yang sudah lo punya.


