VPS Üzerinde Docker Compose ile Uygulamalar Nasıl Dağıtılır
Ubuntu’da Docker Compose v2 için pratik bir üretim rehberi: motoru kurun, ağlar ve volume’larla bir Compose dosyası yazın, sırları yönetin, öne bir ters proxy koyun, paniksiz güncellemeler yapın ve adlandırılmış volume’ları yedekleyin.

Docker Compose, tek bir VPS’te küçük bir yığını çalıştırmanın olağan yoludur: bir web uygulaması, bir veritabanı, bir Redis önbelleği ve bir ters proxy, hepsi bir YAML dosyasında. Kubernetes değildir. Çok düğümlü yedekleme almazsınız. Yeni bir Hiddence VPS’te dakikalar içinde yeniden oluşturabileceğiniz tekrarlanabilir, gözden geçirilebilir bir kurulum alırsınız — çoğu yan proje ve küçük ürünün tam olarak ihtiyacı budur.
Bu makale, Cuma gecesi docker run’dan üretime daha yakın bir şey istediğinizi varsayar. Docker Engine ve Compose eklentisini kuracağız, root olmayan bir dağıtım kullanıcısı oluşturacağız, sağlık kontrolleri ve yeniden başlatma politikaları olan bir Compose dosyası yazacağız, sırları Git’ten uzak tutacağız, internete yalnızca ters proxy portlarını yayınlayacağız ve bir güncelleme ile yedekleme rutini tanımlayacağız. Örnek yığın tipik bir web uygulaması artı PostgreSQL artı Caddy’dir, ancak aynı desen Node, PHP, Python veya bir grup worker için de çalışır.
Tek bir VPS’te Compose neden hâlâ mantıklı
İnsanlar moda olduğu için Kubernetes’e atlar, sonra bir web sitesi için hafta sonunu YAML ile geçirir. Compose okunaklı kalır. Dosyayı sürümlersiniz, ortam değişkenlerini belgelersiniz ve uygulamadan önce bir değişikliği diff’leyebilirsiniz. Yalıtım yeterince iyidir: ağlar ve en az ayrıcalıklı kullanıcılar kullandıysanız ele geçirilmiş bir uygulama konteyneri veritabanı UNIX soketini elinde tutmamalıdır. Kaynak limitleri bir bellek sızıntısının tüm VPS’i dondurmasını durdurur.
- Tek dosya tüm yığını tanımlar
- Adlandırılmış volume’lar konteyner yeniden oluşturmayı atlatır
- İç Docker ağları Postgres’i genel internetten uzak tutar
- restart: unless-stopped çoğu yeniden başlatmayı karşılar
- İlkini aştığınızda ikinci bir VPS’e kopyalamak kolaydır
Gereksinimler
Uygulama artı Postgres için yeterli RAM’i olan bir VPS kullanın. Uygulama + veritabanı + proxy için gerçekçi minimum 2 GB plandır. Ubuntu 24.04 varsayılır. Cgroups’a ne yaptığını okumadan rastgele bir Snap’ten Docker kurmayın; aşağıdaki adımlar Docker’ın resmi apt deposunu kullanır.
- Ubuntu 22.04/24.04, root veya sudo
- 2 GB RAM önerilir (Postgres’siz çok küçük yığınlar için yalnızca 1 GB)
- Otomatik HTTPS istiyorsanız VPS’e işaret eden bir alan adı
- Projeyi sunucuya kopyalamanın bir yolu: Git veya başka
Adım 1: Docker Engine ve Compose v2’yi kurun
Compose v2, eski docker-compose Python ikilisi değil, docker compose (boşluklu) olarak çağrılan bir eklentidir. Güncel güvenlik yamalarını almak için ikisini de Docker’ın deposundan kurun.
ssh root@YOUR_VPS_IP
apt update && apt -y install ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker version
docker compose versionAdım 2: Dağıtım kullanıcısı ve dizin düzeni
Compose’u root olarak çalıştırmak işe yarar, ancak docker grubunda üyeliği olan ayrılmış bir kullanıcı daha temizdir. Projeyi /root’ta değil /opt veya /srv’ye koyun ki yedekler ve izinler açık olsun. .env içeren dizini asla dünyaya yazılabilir yapmayın.
adduser --disabled-password --gecos '' deploy
usermod -aG docker deploy
mkdir -p /srv/app
chown deploy:deploy /srv/app
chmod 750 /srv/app
# Log in as deploy for the rest of the file editing:
# su - deploy
# cd /srv/appAdım 3: Üretim odaklı bir Compose dosyası yazın
Aşağıdaki dosya bir şablondur. Uygulama imajını sizinkiyle değiştirin. Postgres 0.0.0.0:5432’ye yayınlanmaz — yalnızca Caddy 80 ve 443’te yayınlanır. Sağlık kontrolleri, veritabanını hâlâ taşıyan bir uygulamaya ters proxy’nin trafik göndermesini durdurur. İmaj etiketlerini sabitleyin; latest, sürpriz kırılmaların nasıl olduğunu gösterir.
# /srv/app/compose.yaml
services:
caddy:
image: caddy:2.8-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
app:
condition: service_healthy
app:
image: ghcr.io/example/webapp:1.4.2
restart: unless-stopped
env_file: .env
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"]
interval: 10s
timeout: 3s
retries: 10
networks:
- frontend
- backend
db:
image: postgres:16-alpine
restart: unless-stopped
env_file: .env
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- backend
networks:
frontend: {}
backend: {}
volumes:
pgdata:
caddy_data:
caddy_config:Adım 4: Sırlar, .env ve Caddyfile
Parolaları sunucudaki .env’ye koyun, chmod 600 yapın ve o dosyayı asla commit etmeyin. Parolaları openssl rand -base64 32 ile üretin. Caddyfile’ın yalnızca Docker ağındaki uygulama hizmet adına ters proxy yapması gerekir.
# /srv/app/.env (permissions 600)
POSTGRES_USER=app
POSTGRES_PASSWORD=change-me-long-random
POSTGRES_DB=app
# plus any APP_SECRET / NEXTAUTH_SECRET your image needs
# /srv/app/Caddyfile
app.example.com {
encode gzip
reverse_proxy app:8000
}
chmod 600 /srv/app/.env
cd /srv/app
docker compose up -d
docker compose ps
docker compose logs -f --tail=100Adım 5: Güvenlik duvarı, günlükler ve kaynak limitleri
UFW yalnızca 22, 80 ve 443’e izin vermelidir. Docker bazen yayınlanan portlar için UFW’yi atlar; sıkı bir ana makine güvenlik duvarı gerekiyorsa Ubuntu sürümünüz için güncel Docker + UFW etkileşimine bakın veya portları yalnızca 127.0.0.1’de yayınlayıp öne bir ana makine Caddy koyun. Postgres’in VPS’i yememesi için Compose’da bellek limitleri koyun.
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
# Optional in compose.yaml under app or db:
# deploy:
# resources:
# limits:
# memory: 512M
# Logs (do not let json-file grow forever):
# { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
# in /etc/docker/daemon.json then: systemctl restart dockerAdım 6: Güncellemeler ve geri alma
İşe yarayan sıkıcı güncelleme: yeni imajları çekin, up -d, sağlık kontrollerini izleyin, geri sabitlemek için önceki etiketi git’te tutun. Trafiği durdurmayı kastetmiyorsanız üretim veritabanında docker compose down yapmayın. down -v volume’ları siler — bu bir yeniden başlatma değil, silmedir.
cd /srv/app
git pull # if the Compose file lives in git
# Change the image tag in compose.yaml, then:
docker compose pull
docker compose up -d
docker compose ps
# Rollback: set the old tag, pull, up -d again
# NEVER: docker compose down -v # this deletes named volumesAdlandırılmış volume’ları yedekleyin
Bir VPS anlık görüntüsü iyidir. Başka bir makineye geri yüklemeniz gerekiyorsa mantıksal bir Postgres dökümü daha iyidir. Bunu cron ile zamanlayın. Geri yüklemeyi bir kez deneyin, yoksa yedekleriniz yoktur — yedek olduğunu umduğunuz dosyalarınız vardır.
# Postgres dump while the db container is running:
docker compose exec -T db pg_dump -U app app | gzip > /var/backups/app-$(date +%F).sql.gz
# Copy off-box
# scp /var/backups/app-*.sql.gz backup-host:~
# Restore sketch (maintenance window):
# gunzip -c app-2026-08-19.sql.gz | docker compose exec -T db psql -U app appSorun giderme
docker compose ps ve docker compose logs service okuyun. İmaj çekme hataları genellikle ghcr/docker hub hız limitleri veya giriş olmadan özel bir imajdır. Caddy’den 502, uygulamanın sağlıklı olmadığı veya proxy ana bilgisayar adının yanlış olduğu anlamına gelir (Caddy konteynerinin içinden localhost değil Compose hizmet adını kullanın). Volume’larda izin hataları genellikle imajın uid 1000 olarak çalışması, ana makine dizininin ise root olmasıdır.
- compose: command not found — eski ikili adını kurdunuz; docker compose kullanın
- port is already allocated — 80/443’e başka bir şey sahip (Apache, Nginx, başka bir Caddy)
- database connection refused — uygulama Postgres hazır olmadan başladı; healthcheck + depends_on koşulu kullanın
- disk full — docker system df, ardından kullanılmayan imajları dikkatle budayın
- permission denied on docker.sock — kullanıcı docker grubunda değil veya yeni bir oturum gerekir
Güvenlik
Veritabanı portlarını yayınlamayın. privileged: true çalıştırmayın. Kasten bir konteyner yöneticisi yazmıyorsanız /var/run/docker.sock’u bir uygulama konteynerine bağlamayın. Motoru güncel tutun. Kendi imajlarınızı derliyorsanız tarayın. Diskteki .env hâlâ bir sır dosyasıdır — kutuya kimlerin ssh yapabileceğini kısıtlayın.
- Genel 5432 / 3306 / 6379 yok
- İmaj özetlerini veya en azından değişmez etiketleri sabitleyin
- chmod 600 .env
- Ana işletim sistemi için Unattended-upgrades hâlâ önemlidir
- Uygulama başına bir Compose projesi patlama yarıçapını küçük tutar
İpuçları
- compose.yaml’i git’e koyun; gerçek sırlar olmadan .env.example tutun
- İsteğe bağlı worker’lar için profilleri kullanın (docker compose --profile workers up -d)
- VPS’i boyutlandırırken docker stats izleyin
- Kaynak kodunun bağlama bağlanması üretim deseni değildir — bir imaj pişirin
- Yığın birkaç VPS’e büyürse o zaman orkestrasyona bakın — daha önce değil
Tek bir VPS’te üretime yakın bir Compose kurulumu şudur: resmi Docker paketleri, kilitlenmiş bir .env, veritabanını yayınlamayan bir YAML dosyası, sağlık kontrolleri, 80/443’te bir ters proxy, UFW, geri alabileceğiniz imaj etiketleri ve bir kez gerçekten geri yüklediğiniz Postgres dökümleri. Bu makaledeki şablondan başlayın, uygulama imajını değiştirin ve docker compose down -v komutuna rm -rf ile aynı saygıyı gösterin.