Mert Özen Her Satırda Daha İleri
DevOps Linux ve Shell (geliştirici için) — #16

SSH Derinlemesine: ~/.ssh/config, ProxyJump ile Bastion, Port Forwarding ile Uzak Veritabanına Bağlanmak ve Agent Forwarding Riski

Mert Özen 15 Kas 2026 12 dk 10 görüntülenme
SSH Derinlemesine: ~/.ssh/config, ProxyJump ile Bastion, Port Forwarding ile Uzak Veritabanına Bağlanmak ve Agent Forwarding Riski

Bastion üzerinden private sunuculara ssh -A ile değil ProxyJump ile geç: agent forwarding açıkken bastion'da root olan biri senin key'inle veritabanı sunucusuna girebiliyor ve ControlPersist bu pencereyi dakikalarca açık tutuyor. ~/.ssh/config'in ilk-değer-kazanır kuralını, Match ve ControlMaster'ı, -L/-R/-D ile tünelleri, bastion'ı AllowTcpForwarding ve PermitOpen ile daraltmayı, FIDO2 key'leri ve SSH certificate'larını gerçek çıktılarla anlatıyoruz.

Sürüm notu: Bu yazıdaki örnekler OpenSSH 9.6p1 ile Ubuntu Server 24.04.5 LTS (uzun süreli destek sürümü) çalıştıran üç makinelik bir lab'da (istemci, bastion ve veritabanı sunucusu; aynı Docker network'ünde container'lar) denenmiştir. Ubuntu 26.04.1 LTS'teki OpenSSH 10.2p1 ile forward edilen agent socket'inin yeri ayrıca denenmiştir. Windows 11'deki OpenSSH istemcisi ve SSMS (SQL Server Management Studio; SQL Server'ın Windows'taki yönetim aracı) çalıştırılmadı; o kısımlar Microsoft ve Win32-OpenSSH dokümanlarına dayanıyor.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) veritabanı sunucusu internete kapalı; ona yalnızca bir bastion (dışarıdan erişilebilen tek giriş sunucusu) üzerinden ulaşılıyor. Bastion'a ssh -A ile bağlanıp oradan veritabanı sunucusuna geçmek alışkanlık hâline gelmiş: -A agent forwarding'i açıyor ve laptoptaki key'in bastion'dan da kullanılabiliyor. Private key dosyası laptoptan hiç çıkmıyor.

$ ssh -A bastion 'echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK"'
SSH_AUTH_SOCK=/tmp/ssh-63L55kGOzg/agent.644

Komut bitti, oturum kapandı. Bastion'da sudo yetkisi olan biri (ya da bastion'ı ele geçiren bir saldırgan) şimdi senin kimliğinle veritabanı sunucusuna girebilir mi? Cevap "Agent Forwarding'in Riski" bölümünde.

Birinci yazıda SSH'a (uzak bağlantı protokolü) key ile bağlanmayı, tek bir host alias'ını ve basit bir tüneli görmüştük. Bu yazı serinin son yazısı ve SSH'ın günlük işte en çok işe yarayan ama en az bilinen kısımlarını topluyor: ~/.ssh/config'in kuralları, bastion üzerinden private sunuculara geçmek, port forwarding ile veritabanına tünel kurmak ve sunucu tarafında bunları sınırlamak.

~/.ssh/config: Host Pattern'leri, İlk Değer Kazanır ve Match

Lab'daki istemcinin config dosyası. Lab'da HostName olarak container adları duruyor; gerçek hayatta buraya sunucunun DNS adı ya da IP'si gelir:

# ~/.ssh/config
Match originalhost bookstore-db exec "printenv BOOKSTORE_VPN >/dev/null"
    ProxyJump none

Host bastion
    HostName linuxseries-bastion

Host bookstore-db
    HostName linuxseries-db
    ProxyJump bastion

Host bookstore-db-tunnel
    HostName linuxseries-bastion
    LocalForward 127.0.0.1:14330 linuxseries-db:1433
    SessionType none
    ExitOnForwardFailure yes

Host *
    User ada
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    StrictHostKeyChecking accept-new
    ServerAliveInterval 30
    ServerAliveCountMax 3

ssh dosyayı yukarıdan aşağı okur ve bir ayar için bulduğu ilk değeri kullanır; sonradan gelen eşleşmeler o ayarı değiştirmez. Bu yüzden özel bloklar üstte, Host * en altta durur. Ters sırada ne olduğunu küçük bir dosyayla gördüm:

$ cat /tmp/order.conf
Host *
    User ada

Host bastion
    HostName linuxseries-bastion
    User deploy
$ ssh -F /tmp/order.conf -G bastion | grep "^user "
user ada

bastion bloğundaki User deploy hiç uygulanmadı. Bir host için sonunda hangi ayarların geçerli olduğunu ssh -G söyler; config'i değiştirdiğinde ilk bakılacak yer:

$ ssh -G bookstore-db | grep -E "^(user|hostname|port|proxyjump|identityfile|identitiesonly|serveraliveinterval|serveralivecountmax) "
user ada
hostname linuxseries-db
port 22
identitiesonly yes
serveralivecountmax 3
serveraliveinterval 30
identityfile ~/.ssh/id_ed25519
proxyjump bastion

Host satırı birden çok pattern alabilir (Host bookstore-db bookstore-app, Host bookstore-*, ! ile hariç tutma). Match ise koşula bakar: originalhost komut satırına yazdığın adı, host HostName uygulandıktan sonraki adı, exec bir komutun exit code'unu kontrol eder. Dosyanın başındaki blok "VPN'deysem (şirket ağına şifreli tünel) bastion'a gerek yok" diyor; ilk değer kazandığı için en üstte:

$ ssh -G bookstore-db | grep "^proxyjump "
proxyjump bastion
$ BOOKSTORE_VPN=1 ssh -G bookstore-db | grep -c "^proxyjump "
0

IdentitiesOnly yes ssh'ın agent'taki bütün key'leri sırayla denemesini engeller; birinci yazıdaki "Too many authentication failures" hatasının ilacı. ServerAliveInterval 30 bağlantı boştayken 30 saniyede bir sunucuya şifreli bir yoklama gönderir, ServerAliveCountMax 3 üç cevapsız yoklamadan sonra bağlantıyı kapatır. Böylece arada bir firewall ya da NAT (adres çevirisi yapan ağ cihazı) boştaki bağlantıyı unutsa da oturum açık kalır, ağ gerçekten koptuysa terminalin dakikalarca donmaz.

Windows 11'deki OpenSSH istemcisi aynı dosyayı C:\Users\user\.ssh\config'ten okur ve bu bölümdeki ayarlar orada da geçerli. Bir istisna var: aşağıda göreceğimiz ControlMaster Win32-OpenSSH'ta desteklenmiyor; projenin bu konudaki issue'su hâlâ backlog'da. Windows'ta bu satırları ya koyma ya da WSL'deki (Windows içinde Linux çalıştıran katman) ssh'ı kullan. Windows tarafını çalıştırmadım.

ProxyJump: Bastion Üzerinden Private Sunuculara

Veritabanı sunucusu yalnızca bastion'dan gelen bağlantıları kabul ediyor:

$ ssh -o ProxyJump=none -o ConnectTimeout=5 bookstore-db hostname
ssh: connect to host linuxseries-db port 22: Connection timed out
$ ssh bookstore-db hostname
linuxseries-db
$ ssh -v bookstore-db true 2>&1 | grep -i -E "proxy|Authenticated|Connecting to"
debug1: Setting implicit ProxyCommand from ProxyJump: ssh -v -W '[%h]:%p' bastion
debug1: Executing proxy command: exec ssh -v -W '[linuxseries-db]:22' bastion
debug1: Connecting to linuxseries-bastion [<ip>] port 22.
Authenticated to linuxseries-bastion ([<ip>]:22) using "publickey".
Authenticated to linuxseries-db (via proxy) using "publickey".

ProxyJump (komut satırında -J) önce bastion'a bağlanır ve -W ile bastion'dan hedefin 22. port'una ham bir TCP kanalı açar. Hedefle SSH konuşması bu kanalın içinden, laptoptan hedefe uçtan uca yapılır: iki ayrı "Authenticated" satırı bunu gösteriyor. Bastion trafiği taşır ama şifresini çözemez, key'ine de ihtiyaç duymaz. Tek seferlik kullanımda config'e gerek yok: ssh -J ada@bastion-adresi ada@db-adresi.

16-ssh-bastion-tunnel-tr.png

ControlMaster: Bağlantıyı Paylaşmak

Host * bloğuna üç satır ekledim:

    ControlMaster auto
    ControlPath ~/.ssh/cm-%C
    ControlPersist 10m
$ time ssh bookstore-db hostname
linuxseries-db

real	0m0.282s
user	0m0.034s
sys	0m0.001s
$ time ssh bookstore-db hostname
linuxseries-db

real	0m0.045s
user	0m0.002s
sys	0m0.001s
$ ssh -O check bookstore-db
Master running (pid=268)
$ ls ~/.ssh/ | grep cm-
cm-301cd87b27fbfcd54676dbd2e64207a1149b5e98
cm-6de0cd5011a6c1e35bdbe7821fa6f4d9330d07ec
$ ssh -O exit bookstore-db
Exit request sent.

İlk bağlantı bir master oluşturur ve socket'ini ControlPath'e koyar; sonraki bağlantılar yeni bir SSH handshake'i yapmadan bu bağlantının içinden açılır. Aynı ağdaki lab'da bile süre altıda birine indi; uzak bir sunucuda ve bastion üzerinden fark saniyeler olur. İki socket var, çünkü ProxyJump'ın bastion bağlantısı da paylaşılıyor. %C bağlantı bilgilerinden (yerel makine, host, port, kullanıcı) üretilen bir hash. ControlPersist 10m son oturum kapandıktan sonra master'ı 10 dakika açık tutar; bu süre aşağıda agent forwarding'le birlikte önem kazanacak.

Port Forwarding: -L, -R ve -D

Bookstore'un SQL Server'ı 1433'te dinliyor ve o port'a da yalnızca bastion ulaşabiliyor. SSMS ile bağlanmak için config'teki bookstore-db-tunnel bloğu tek komutluk bir tünel tanımlıyor. Lab'da SQL Server yerine 1433'te bağlantıyı kabul edip tek satır yazan bir test listener'ı var; SQL Server image'ı lisans kabulü istediği için onu çalıştırmadım.

$ nc -w 3 linuxseries-db 1433; echo "exit=$?"
exit=1
$ ssh -f bookstore-db-tunnel
$ ss -tln | grep 14330
LISTEN 0      128        127.0.0.1:14330      0.0.0.0:*
$ nc -w 3 127.0.0.1 14330
linuxseries-db: test listener on 1433 (stand-in for SQL Server)
$ ssh -O exit bookstore-db-tunnel
Exit request sent.

LocalForward 127.0.0.1:14330 linuxseries-db:1433, komut satırındaki -L 127.0.0.1:14330:linuxseries-db:1433'ün karşılığı: laptopta 14330'u dinle, gelen her bağlantıyı SSH içinden bastion'a taşı ve bastion'ın gözünden linuxseries-db:1433'e bağla. Hedef adresi bastion çözer; laptopun o adı bilmesine gerek yok. SessionType none uzakta komut çalıştırmaz (-N), ExitOnForwardFailure yes port dolu olduğu için tünel kurulamazsa ssh'ı sessizce açık bırakmak yerine hata ile çıkar. SSMS'te sunucu adı olarak 127.0.0.1,14330 yazılır (SQL Server port'u virgülle ayırır); tünel sertifikadaki adı değiştirdiği için bağlantı ayarlarında sertifika doğrulamasını da düşünmen gerekir. SSMS kısmını çalıştırmadım.

-R ters yönde çalışır: sunucuda bir port açar ve gelen bağlantıyı senin makinene taşır. Örneğin sunucudan gelecek bir callback'i kendi makinende çalışan uygulamaya almak için:

$ ssh -f -N -R 9090:127.0.0.1:5080 bastion
# bastion'da:
$ ss -tln | grep 9090
LISTEN 0      128        127.0.0.1:9090       0.0.0.0:*
LISTEN 0      128            [::1]:9090          [::]:*
$ curl -s http://127.0.0.1:9090/ping
{"received":true,"from":"local dev"}

Port sunucuda yalnızca loopback'te açıldı; sunucunun GatewayPorts no varsayılanı onu dışarı açmıyor. -D ise laptopta bir SOCKS (uygulamanın bağlantılarını başka bir makine üzerinden açtıran proxy protokolü) proxy'si açar; tarayıcı ya da curl o proxy'yi kullandığında bağlantılar bastion'dan çıkar:

$ ssh -f -N -D 1080 bastion
$ curl -sS --max-time 5 http://linuxseries-db:8080/health
curl: (28) Connection timed out after 5003 milliseconds
$ curl -sS --socks5-hostname 127.0.0.1:1080 http://linuxseries-db:8080/health
{"status":"Healthy","server":"linuxseries-db"}

Private ağdaki bir yönetim sayfasına bakmak için pratik, ama aynı nedenle bastion'da sınırlamak isteyeceğin bir özellik.

Agent Forwarding'in Riski

Tahminin cevabı evet. Bastion'da sudo yetkisi olan bir kullanıcıyla, senin ssh -A komutun bittikten sonra şunu yaptım:

$ sudo find /tmp -type s -name "agent.*"
/tmp/ssh-63L55kGOzg/agent.644
$ sudo SSH_AUTH_SOCK=/tmp/ssh-63L55kGOzg/agent.644 ssh-add -l
256 SHA256:mYAvoGnZ9vvvXaJmqLUi6CvE2dnmYEbrJdJgitJbDSU ada@linuxseries-client (ED25519)
$ sudo SSH_AUTH_SOCK=/tmp/ssh-63L55kGOzg/agent.644 ssh -o StrictHostKeyChecking=accept-new ada@linuxseries-db hostname
Warning: Permanently added 'linuxseries-db' (ED25519) to the list of known hosts.
linuxseries-db

Agent forwarding key'i bastion'a kopyalamaz; bastion'da bir socket açar ve oraya gelen imza isteklerini laptoptaki agent'a taşır. Socket'e erişebilen herkes (root her zaman erişebilir) o socket açık olduğu sürece senin key'inle imza attırabilir. Key dosyası çalınmadı, ama gerek de kalmadı: veritabanı sunucusuna senin adınla girildi. İşi kötüleştiren bir ayrıntı daha var: komut bittiği hâlde socket duruyordu, çünkü ControlPersist master bağlantıyı 10 dakika açık tutuyor ve forward edilen agent da o bağlantıyla birlikte yaşıyor. Master'ı kapatınca ve aynı işi ProxyJump ile yapınca bastion'da agent socket'i kalmadı:

$ ssh -O exit bastion
Exit request sent.
$ ssh bookstore-db hostname
linuxseries-db
# bastion'da:
$ sudo find /tmp -type s -name "agent.*" | wc -l
0

Kural basit: agent forwarding'i bastion'a geçmek için kullanma, onun işi ProxyJump. Gerçekten gerekiyorsa (örneğin sunucudan GitHub'a SSH ile git pull) yalnızca güvendiğin, başka kimsenin root olmadığı sunucuda ve o host için aç, ssh-add -c ile her kullanımda onay iste. Ubuntu 26.04'teki OpenSSH 10.2'de forward edilen socket /tmp yerine kullanıcının ~/.ssh/agent/ klasöründe açılıyor (OpenSSH 10.1 değişikliği). Denedim: root yine aynı şekilde ssh-add -l ile key'i listeleyebildi; yer değişti, risk değişmedi.

Bastion'ı Sunucu Tarafında Daraltmak

İstemciyi ne kadar dikkatli ayarlarsan ayarla, sınırı sunucu koyar. Ubuntu'nun varsayılanları bastion için fazla açık:

$ sudo sshd -T | grep -E "^(allowagentforwarding|allowtcpforwarding|gatewayports|permitopen) "
gatewayports no
allowtcpforwarding yes
allowagentforwarding yes
permitopen any

On birinci yazıdaki sertleştirmenin üstüne bastion'a bir drop-in daha ekledim; repoda deploy/linux/ssh/bastion/sshd_config.d/10-bastion.conf:

# /etc/ssh/sshd_config.d/10-bastion.conf
# Bastion: only a hop to the private servers
AllowAgentForwarding no
AllowTcpForwarding local
PermitOpen linuxseries-db:22 linuxseries-db:1433
X11Forwarding no
GatewayPorts no

sshd -t ve reload'dan sonra (on dördüncü yazıdaki sıra) istemciden denedim:

$ ssh -A bastion 'echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK"'
SSH_AUTH_SOCK=
$ ssh bookstore-db hostname
linuxseries-db
$ ssh -N -o ExitOnForwardFailure=yes -R 9090:127.0.0.1:5080 bastion
Error: remote port forwarding failed for listen port 9090
$ ssh -N -o ControlMaster=no -L 18080:linuxseries-db:8080 bastion &
$ curl -sS http://127.0.0.1:18080/health
channel 2: open failed: administratively prohibited: open failed
curl: (56) Recv failure: Connection reset by peer

Agent forwarding kapandı, ProxyJump çalışıyor, -R reddedildi ve PermitOpen listesinde olmayan hedefe tünel açılamadı. AllowTcpForwarding'i no yapmak daha güvenli görünebilir ama bastion'ı işe yaramaz hâle getirir, çünkü ProxyJump'ın -W'si de bir local forwarding:

$ ssh bookstore-db hostname
Connection closed by UNKNOWN port 65535

İstemcideki mesaj bir şey söylemiyor; sebep bastion'daki sshd log'unda (Ubuntu'da journalctl -u ssh): refused local port forward: originator 127.0.0.1 port 65535, target linuxseries-db port 22. Uygulama sunucusu ve veritabanı sunucusu için durum tersi: oradan bir yere tünel açılmasına gerek yoksa AllowTcpForwarding no ve AllowAgentForwarding no doğru varsayılan.

FIDO2 Key'ler ve SSH Certificate'larına Giriş

Private key dosyası çalınabilir. FIDO2 (donanım güvenlik anahtarlarının kullandığı standart) key'lerde (ed25519-sk) private key bilgisayara takılan ya da telefona okutulan bir donanım güvenlik anahtarının içinde kalır, diskteki dosya ona yalnızca bir referanstır ve her bağlantıda anahtara dokunman gerekir. Lab'da donanım anahtarı olmadığı için yalnızca hata mesajını görebildim:

$ ssh-keygen -t ed25519-sk -f ~/.ssh/id_ed25519_sk -N ""
Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.
Key enrollment failed: device not found

Sunucu sayısı ve ekip büyüdükçe her sunucudaki authorized_keys'i yönetmek zorlaşır. SSH certificate'ında bir CA (key'leri imzalayan otorite) senin public key'ini imzalar; sunucular authorized_keys yerine CA'ya güvenir. Bir CA oluşturup key'imi deploy kullanıcısı için 8 saatliğine imzaladım:

$ ssh-keygen -q -t ed25519 -N "" -C bookstore-user-ca -f ~/ca/bookstore-user-ca
$ ssh -o BatchMode=yes -l deploy bastion whoami
deploy@linuxseries-bastion: Permission denied (publickey,password).
$ ssh-keygen -s ~/ca/bookstore-user-ca -I ada-2026-11-15 -n deploy -V +8h ~/.ssh/id_ed25519.pub
Signed user key /home/ada/.ssh/id_ed25519-cert.pub: id "ada-2026-11-15" serial 0 for deploy valid from 2026-11-15T07:40:00 to 2026-11-15T15:41:58
$ ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub
/home/ada/.ssh/id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:mYAvoGnZ9vvvXaJmqLUi6CvE2dnmYEbrJdJgitJbDSU
        Signing CA: ED25519 SHA256:5f4oWzbXoMc3tH45BLHhyB8n9+yFIAtVFCgu4VGRTBY (using ssh-ed25519)
        Key ID: "ada-2026-11-15"
        Serial: 0
        Valid: from 2026-11-15T07:40:00 to 2026-11-15T15:41:58
        Principals:
                deploy
        Critical Options: (none)
        Extensions:
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

Bastion'da yalnızca tek satır gerekiyor: TrustedUserCAKeys /etc/ssh/bookstore-user-ca.pub. deploy kullanıcısının authorized_keys dosyası yok, buna rağmen aynı komut artık çalıştı ve sshd log'u girişi certificate'ın kimliğiyle kaydetti:

$ ssh -o BatchMode=yes -l deploy bastion whoami
deploy

Certificate'ın süresi dolunca erişim kendiliğinden biter; ayrılan bir çalışanın key'ini her sunucudan silmek gerekmez. Bu bir giriş: CA'nın private key'i laptopta değil, bir secret store'da ya da ayrı bir imzalama servisinde durmalı. Bu yazıda o altyapıyı kurmuyoruz.

Üretimde Dikkat

Bastion'ı tek iş için kullan: atlamak. Üzerinde uygulama, araç ya da başka kullanıcıların shell'i olmasın; ne kadar az şey çalışırsa agent socket'ine ya da tünellere erişebilecek o kadar az şey olur. Bastion'ın kendi log'u (kim, ne zaman, hangi hedefe) denetim için değerli; on birinci yazıdaki journalctl _COMM=sshd burada da geçerli.

Tünelleri kalıcı servis gibi kullanma. Uygulamanın veritabanına bağlantısı SSH tüneline değil, private network'e ve firewall kurallarına dayanmalı; tünel geliştiricinin geçici erişimi içindir. Geçici olduğunu unutma: ssh -O exit ya da ss -tlnp ile açık kalanları kontrol et.

CI'dan sunucuya deploy (GitHub Actions serisinin "Kendi Linux Sunucuna SSH" yazısı) aynı kuralları izler: ayrı bir deploy key'i, known_hosts'a önceden yazılmış host key ve gerekiyorsa ProxyJump. CI'da agent forwarding'e hiç gerek yok.

Sık Düşülen Tuzaklar

Tüneli 0.0.0.0'a bind etmek. Lab'da denedim:

$ ssh -f -N -L 0.0.0.0:14331:linuxseries-db:1433 bastion
$ ss -tln | grep 14331
LISTEN 0      128          0.0.0.0:14331      0.0.0.0:*
# aynı ağdaki başka bir makineden:
$ nc -w 3 linuxseries-client 14331
linuxseries-db: test listener on 1433 (stand-in for SQL Server)

Laptopunla aynı ağdaki herkes (kafedeki Wi-Fi dahil) artık private veritabanına bağlanabiliyor; firewall'u senin tünelin delmiş oldu. Bind adresini boş bırak ya da açıkça 127.0.0.1 yaz; -g ve GatewayPorts yes da aynı sonucu doğurur.

Agent forwarding'i bastion'a geçmek için açmak, hele ControlPersist ile. ProxyJump kullan ve bastion'da AllowAgentForwarding no.

Host *'ı dosyanın başına yazmak. İlk değer kazanır; özel bloklar uygulanmaz. Şüphede ssh -G.

Bastion'da AllowTcpForwarding no ile ProxyJump'ı kırmak ve istemcideki anlamsız hata mesajıyla vakit kaybetmek. Sebep sunucu log'unda.

Küçük Bir Deneme

★☆☆ Kendi ~/.ssh/config'ini aç ve her host için ssh -G çıktısına bak. Beklemediğin bir user ya da identityfile var mı?

★★☆ İki VM ya da container ile bir bastion ve bir private sunucu kur. Önce ssh -A ile, sonra ProxyJump ile bağlan ve her durumda bastion'da sudo find /tmp -type s çıktısını karşılaştır. ControlPersist'i kaldırınca ne değişiyor?

★★★ Bastion'a PermitOpen ile yalnızca veritabanının 1433 port'una izin ver, sonra -L, -D ve -J ile hangi bağlantıların çalıştığını tabloya dök. Bir SSH certificate'ını -O no-port-forwarding ile imzalayıp aynı testi tekrarla.

Seriyi burada bitiriyoruz. SSH ile bir sunucuya bağlanmaktan başlayıp file system'i, permission'ları, process'leri, systemd'yi, log okumayı, network'ü, paketleri, kaynakları, sertleştirmeyi, sorun gidermeyi ve sürüm geçişlerini gördük; son üç yazıda sunucuda dosya düzenlemeyi, dosyaları paketleyip taşımayı ve SSH'ın bastion, tünel ve agent tarafını ekledik. Hepsi aynı Bookstore sunucusu üzerinde, her ayar repoda. Burada öğrendiklerini script'e dökmek Bash Scripting serisinde, aynı uygulamayı container ile çalıştırmak Docker serisinde devam ediyor.