Teknoloji

VPS’te Docker Kurulumu: Compose ve Kalıcı Veri

VPS Üzerinde Docker ile Uygulama Çalıştırmak

VPS üzerinde Docker kullanmak, uygulama bağımlılıklarını ayrı imajlarda tutmanıza ve servisleri bir Compose dosyasıyla yönetmenize yardımcı olur.

Bir web uygulamasını ya da küçük bir SaaS projesini canlıya alırken en sık karşılaşılan sorulardan biri şudur: “Aynı VPS üzerinde birden fazla uygulamayı nasıl güvenli ve birbirinden izole şekilde çalıştırırım?” Klasik yaklaşım; her proje için ayrı kullanıcı, ayrı dizin, ayrı port, elle servis kurulumları ve bolca konfigürasyon dosyası ile uğraşmaktır. Birkaç uygulamaya kadar bu yöntem idare edebilir; ancak güncelleme, rollback, bağımlılık çatışmaları ve güvenlik tarafında işler hızla karışır.

Bu rehber, temiz Ubuntu 24.04 LTS üzerinde Docker Engine ve Compose kurulumundan Node.js, Nginx ve PostgreSQL içeren küçük bir projeye ilerler. Konteynerler aynı çekirdeği paylaşır: ayrı ağlar ve kaynak limitleri, birbirine güvenmeyen müşteriler için tek başına yeterli bir güvenlik sınırı değildir. Örnek aynı yöneticiye ait servisler içindir; internet yayınına geçmeden önce TLS, erişim politikası ve geri yükleme planı ayrıca tamamlanmalıdır.

Temel Kavramlar: VPS, Konteyner ve Docker İlişkisi

Önce mimariyi netleştirelim. Elinizde bir VPS olduğunu düşünün. Bu sanal sunucunun CPU, RAM ve disk kapasitesi seçilen plana bağlıdır; CPU kaynakları paylaşımlı veya ayrılmış olabilir. Docker kurulumu için planın sanallaştırma türünü, desteklediği Linux dağıtımlarını (Ubuntu, Debian, AlmaLinux vb.) ve root ya da sudo erişimini doğrulayın.

Konteyner, ayrı bir sanal makine yerine işletim sistemi seviyesinde izolasyon sağlayan hafif bir çalışma ortamıdır. Tipik bir Docker yapılandırmasında dosya sistemi, ağ ve süreç alanları ayrılır; ancak konteynerler VPS’in çekirdeğini (kernel) paylaşır. İzolasyonun kapsamı yapılandırmaya bağlıdır. Bu sayede:

  • VM’lere göre genellikle daha hafiftir ve daha hızlı başlarlar,
  • Ayrı bir konuk işletim sistemi çalıştırmadıkları için ek kaynak tüketimini azaltabilirler,
  • “Aynı benim makinemde çalışıyordu” sorununu azaltacak şekilde tekrarlanabilir ortam sunarlar.

Docker, imaj oluşturma, konteyner yaşam döngüsü, ağ ve depolama işlemlerini yönetir. İmaj uygulama dosyalarını ve çalışma ortamını paketler; veritabanı verileri, parolalar ve host güvenlik ayarları bu paketin dışında yönetilir. Tekrarlanabilirlik için imaj sürümleri, bağımlılık kilidi ve kurulum dosyaları birlikte korunmalıdır.

Docker ile VPS’te İzole Uygulama Barındırmanın Avantajları

VPS üzerinde Docker kullanmanın başlıca avantajları şunlardır:

  • Bağımlılık ayrımı: Farklı uygulamalar desteklenen farklı runtime sürümleriyle çalışabilir. Yayımlanan host portları yine çakışmamalıdır.
  • Geri dönüş: Önceki imajı yeniden çalıştırmak mümkündür; veritabanı şeması ve kalıcı veriler kendiliğinden geri alınmaz.
  • Tekrarlanabilir kurulum: Dockerfile, kilit dosyası ve Compose tanımı taşınabilir; yeni hostta veri, sırlar ve ağ ayarları ayrıca hazırlanır.
  • Kontrollü güncelleme: Sürüm etiketi veya digest ile test edilmiş imajı seçebilir, planlı biçimde yenileyebilirsiniz.
  • Kaynak sınırları: CPU, bellek ve süreç limitleri tek servisin etkisini azaltır. Disk dolması, çekirdek sorunları veya aynı VPS’in kaybı bütün servisleri etkileyebilir.

VPS tarafında kaynak planlama konusunda daha derinlemesine okumak isterseniz, CPU, RAM ve disk kaynaklarını doğru seçme rehberimize de göz atabilirsiniz.

Adım 1: Doğru VPS ve Linux Dağıtımını Seçmek

Aşağıdaki komutlar sudo yetkili bir kullanıcı, çalışan SSH erişimi ve temiz Ubuntu 24.04 LTS kurulumu varsayar. Bu rehberde seçilen bütün imajlarla uyumluluk için amd64 veya arm64 mimarisini kullanın; Docker’ın desteklediği her mimari bu imaj etiketlerinde bulunmaz. Daha önce Docker, Podman, containerd veya bir hosting paneli kurulmuş sunucuda paketleri körlemesine değiştirmeyin; önce mevcut servisleri ve Docker’ın paket çakışması listesini değerlendirin.

  • Ubuntu 24.04 LTS: Bu rehberin APT deposu Ubuntu Noble için tanımlıdır.
  • Debian: Desteklenen sürüm ve mimariyi Docker’ın Debian kurulum sayfasından seçin; Ubuntu deposunu eklemeyin.
  • Diğer dağıtımlar: İlgili dağıtımın Docker kurulum yolu ve destek durumunu kontrol edin. Paket yöneticisi ve güvenlik politikaları değişir.

Önce işletim sistemini güncelleyin, gerekiyorsa yeniden başlatın ve SSH anahtarıyla erişiminizi doğrulayın. Yönetici erişimini koruyarak host güvenlik duvarını hazırlayın. Bellek ve CPU ihtiyacını uygulama, veritabanı, derleme, log ve yedek yükünü birlikte ölçerek belirleyin; sabit bir RAM miktarı belirli sayıda konteyner garantilemez. Ayrıntılı adımlar için yeni VPS’te ilk 24 saatte yapılması gerekenler rehberimizi izleyebilirsiniz.

Adım 2: VPS Üzerinde Docker Kurulumu

Ubuntu Üzerinde Docker Kurulumu

Docker’ın resmî Ubuntu deposundan Engine, Buildx ve Compose eklentisini kurun. Komutlar yalnız temiz Ubuntu 24.04 içindir; loopback örnekleri Docker Engine 28 veya üzerini varsayar; mevcut APT kaynaklarını veya çalışan Docker kurulumunu değiştiren bir yükseltme tarifi değildir.

sudo apt update
sudo apt install -y ca-certificates curl openssl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: noble
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version
sudo docker run --rm hello-world

Bu rootful kurulumda aşağıdaki Docker komutları sudo kullanır. İsteğe bağlı docker grubu üyeliği sudo yazma ihtiyacını kaldırır, fakat kullanıcıya root düzeyinde yetki verir. Yalnız güvenilir yönetici hesabında kullanın; uygulama veya müşteri kullanıcılarını bu gruba eklemeyin.

sudo usermod -aG docker "$USER"

Bu isteğe bağlı adımdan sonra oturumu kapatıp yeniden açın. Rootless Docker ayrı bir kurulum yoludur; yalnız gruba eklemek veya imajda USER seçmek rootless daemon oluşturmaz.

Docker Compose Kurulumu

Compose, bir projedeki servis, ağ, volume ve secrets tanımlarını tek dosyada toplar. Kurulum adımı docker-compose-plugin paketini zaten içerir; güncel komut yazımı docker compose biçimindedir.

sudo docker compose version

Sürüm çıktısı eklentinin kullanılabildiğini gösterir. Bir Compose projesini çalıştırmadan önce dosyasını docker compose config ile çözümleyin; bu kontrol tek başına imajın başlayacağını veya uygulamanın sağlıklı olduğunu kanıtlamaz.

Adım 3: İlk Konteynerinizi Çalıştırmak

Docker kurulumunun sağlıklı olduğunu doğrulamanın en basit yolu, küçük bir test konteyneri çalıştırmaktır:

sudo docker run --rm hello-world

Bu komut, test imajını indirip kısa bir bilgi mesajı yazdırarak konteyneri sonlandırır. Ardından ufak bir web servisi deneyebiliriz. Örneğin Nginx:

sudo docker run -d --name demo-nginx --restart unless-stopped \
  --publish 127.0.0.1:8081:80 nginx:1.30.6
curl --fail http://127.0.0.1:8081/

Bu komut:

  • -d arka planda çalıştırır; --name demo-nginx bu alıştırmaya özel bir isim verir.
  • --publish 127.0.0.1:8081:80 yalnız VPS’in IPv4 loopback adresinde host 8081 portunu konteyner 80 portuna bağlar.
  • --restart unless-stopped elle durdurulmadığı sürece çıkış sonrası yeniden başlatmayı sağlar; VPS açılışında Docker servisinin de başlaması gerekir.
  • nginx:1.30.6 seçilmiş bir imaj sürümüdür. Etiketler değiştirilebilir; dağıtımda digest sabitleme ve planlı güvenlik güncellemesi birlikte düşünülmelidir.

curl komutunu VPS üzerinde çalıştırın. Bu adres kendi bilgisayarınızdan sunucu IP’siyle açılmaz. 8081 portu veya demo-nginx adı doluysa önce hangi servis tarafından kullanıldığını inceleyin. Daha sonraki Compose örneği ayrı 8080 portunu kullanır; aynı host portunu iki konteyner yayımlayamaz.

Temel Docker Komutlarına Kısa Bakış

  • sudo docker ps: çalışan konteynerler; sudo docker ps -a: duranlar da dahil tüm konteynerler.
  • sudo docker logs --tail=100 demo-nginx: sınırlı son log çıktısı.
  • sudo docker stop demo-nginx: durdurma; ardından sudo docker rm demo-nginx: bu alıştırma konteynerini kaldırma.
  • sudo docker images: yerel imajlar. Kullanılmayan bir imajı silmeden önce hangi servislerin ona ihtiyaç duyduğunu kontrol edin.

Adım 4: Basit Bir Uygulamayı Konteynerleştirmek

Şimdi yalnız standart HTTP yanıtı veren, daha sonra PostgreSQL’den zamanı okuyabilen küçük bir Node.js uygulaması hazırlayalım. Node 22 bakım desteği 30 Nisan 2027’ye kadar sürer; örnek 22.23.4-bookworm-slim imajını ve pg 8.23.1 sürümünü kullanır. Bunları süresiz sabitlemek yerine güvenlik güncellemelerini test ederek yenileyin.

Örnek Proje Yapısı

Yeni proje dizinini oluşturun; bundan sonraki dosya yolları bu dizine göredir. Dosyaları aşağıdaki içeriklerle kaydedin. package-lock.json sonraki adımda üretilir; secrets dosyaları Compose bölümünde oluşturulur.

mkdir -p "$HOME/docker-vps-demo/app" "$HOME/docker-vps-demo/db"
cd "$HOME/docker-vps-demo"
docker-vps-demo/
  app/
    package.json
    package-lock.json
    server.js
    Dockerfile
    .dockerignore
  db/init-app.sh
  secrets/postgres_password.txt
  secrets/app_db_password.txt
  .gitignore
  compose.yaml
  nginx.conf

app/package.json

{
  "name": "docker-vps-demo",
  "version": "1.0.0",
  "private": true,
  "engines": {
    "node": "22.x"
  },
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "pg": "8.23.1"
  }
}

app/server.js: / örnek yanıtı, /healthz süreç sağlığını gösterir. DB_HOST yokken veritabanı devre dışıdır; /db-time 503 döner. Compose bölümünde veritabanı bağlandığında /readyz gerçek sorguyla hazır olmayı denetler. Bu bir kimlik doğrulamalı müşteri uygulaması değildir; başlangıçta yalnız loopback üzerinden erişilir.

const http = require('node:http');
const fs = require('node:fs');
const { Pool } = require('pg');

function createServer(pool = null) {
  return http.createServer(async (req, res) => {
    const send = (code, body) => {
      res.writeHead(code, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
      res.end(JSON.stringify(body));
    };
    if (req.method !== 'GET') {
      res.setHeader('Allow', 'GET');
      return send(405, { error: 'method_not_allowed' });
    }
    let path;
    try { path = new URL(req.url, 'http://localhost').pathname; }
    catch { return send(400, { error: 'invalid_url' }); }
    if (path === '/healthz') return send(200, { status: 'ok' });
    if (path === '/') return send(200, { message: 'Docker VPS demo' });
    if (path === '/readyz' && !pool) return send(200, { status: 'ok', database: 'disabled' });
    if (path !== '/readyz' && path !== '/db-time') return send(404, { error: 'not_found' });
    if (!pool) return send(503, { error: 'database_not_configured' });
    try {
      const result = await pool.query('SELECT CURRENT_TIMESTAMP AS server_time');
      return send(200, path === '/readyz' ? { status: 'ok' } : { server_time: result.rows[0].server_time });
    } catch {
      return send(503, { error: 'database_unavailable' });
    }
  });
}

if (require.main === module) {
  const pool = process.env.DB_HOST ? new Pool({
    host: process.env.DB_HOST,
    port: 5432,
    database: 'demo',
    user: 'demo_app',
    password: fs.readFileSync(process.env.DB_PASSWORD_FILE, 'utf8').trim(),
    max: 5,
    connectionTimeoutMillis: 2000,
    idleTimeoutMillis: 10000,
    statement_timeout: 2000,
    query_timeout: 3000
  }) : null;
  if (pool) pool.on('error', () => console.error('Database connection lost'));
  const server = createServer(pool);
  server.requestTimeout = 10000;
  server.headersTimeout = 10000;
  server.listen(3000, '0.0.0.0', () => console.log('Listening on port 3000'));
  let stopping = false;
  const stop = () => {
    if (stopping) return;
    stopping = true;
    const deadline = setTimeout(() => process.exit(1), 8000);
    deadline.unref();
    server.close(async () => {
      if (pool) await pool.end();
      clearTimeout(deadline);
    });
  };
  process.on('SIGTERM', stop);
  process.on('SIGINT', stop);
}
module.exports = { createServer };

Dockerfile Yazmak

app/Dockerfile

FROM node:22.23.4-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force
COPY server.js ./
ENV NODE_ENV=production
USER node
EXPOSE 3000
CMD ["node", "server.js"]

app/.dockerignore

*
!package.json
!package-lock.json
!server.js
!Dockerfile

.gitignore

secrets/
backups/
.env
app/node_modules/

Konteyner node kullanıcısıyla çalışır. Yalnız manifest, kilit ve server.js kopyalanır; host node_modules, Git geçmişi ve sırlar build context’e alınmaz. EXPOSE 3000 açıklayıcı metadatadır; hostta port açmaz. Bu örnekte kilitlenen pg bağımlılık ağacının yayımlanmış npm paketleri gerekli JavaScript dosyalarını içerir; bu nedenle npm ci –ignore-scripts ile kurulum yapılır. Kilit dosyasını yenilediğinizde bağımlılıkların derleme veya kurulum scripti ihtiyacını yeniden kontrol edin.

Proje kökünden önce kilit dosyasını üretin ve sürüm kontrolüne alın. Komut hosta Node kurmaz; tek seferlik Node konteyneri host kullanıcı kimliğiyle yalnız app dizinine yazar. Sonraki imaj build işlemi npm ci ile kilitteki bağımlılıkları kurar.

sudo docker run --rm --user "$(id -u):$(id -g)" \
  --env npm_config_cache=/tmp/npm-cache \
  --mount "type=bind,src=$PWD/app,dst=/work" --workdir /work \
  node:22.23.4-bookworm-slim npm install --package-lock-only --ignore-scripts
sudo docker build -t docker-vps-demo:1.0 ./app
sudo docker run -d --name node-demo --restart unless-stopped \
  --publish 127.0.0.1:3000:3000 \
  --memory 256m --cpus 0.50 --pids-limit 100 \
  --read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m \
  --cap-drop ALL --security-opt no-new-privileges:true \
  --log-driver local --log-opt max-size=10m --log-opt max-file=3 \
  docker-vps-demo:1.0
curl --fail http://127.0.0.1:3000/

VPS’teki curl yanıtı uygulamanın başladığını gösterir. Bu bağımsız denemede veritabanı bağlı değildir. Bellek, CPU, süreç ve log değerleri örnek sınırlar olup kapasite tavsiyesi değildir; gerçek uygulama yükünde ölçün. Compose aynı uygulama imajını farklı konteynerde, yayımlanmayan uygulama portuyla kullanacaktır.

Adım 5: Docker Compose ile Çoklu Servisi Yönetmek

Birden fazla servis gerektiğinde her sorumluluğu ayrı tanımlamak güncelleme ve veri yönetimini kolaylaştırır. Her projede bütün bileşenlerin bulunması gerekmez:

  • Uygulama sunucusu (Node.js, PHP-FPM, Python vb.)
  • Veritabanı (MySQL/MariaDB, PostgreSQL)
  • Önbellek (Redis, Memcached)
  • Reverse proxy (Nginx/Caddy) ve SSL sonlandırma

Bu örnekte Node.js uygulaması, Nginx ve PostgreSQL birlikte çalışır. Redis veya TLS servisi eklenmez. Proje kökündeki dosyalar ve iki parola hazır olmadan Compose’u başlatmayın; aşağıdaki akış bütün gerekli dosyaları verir.

Tam Compose Projesi: Uygulama, Nginx ve PostgreSQL

Önce yeni proje için iki rastgele parola oluşturun. Komut mevcut dosyaları değiştirmez. secrets dizini hostta 0700, dosyalar 0444 olur: üst dizin host kullanıcılarını sınırlar; izin verilen konteynerdeki root olmayan süreç bağlanan dosyayı okuyabilir. Bu dosyalar şifrelenmiş depolama değildir.

umask 077
mkdir -p secrets
chmod 700 secrets
test ! -e secrets/postgres_password.txt && openssl rand -hex 32 > secrets/postgres_password.txt
test ! -e secrets/app_db_password.txt && openssl rand -hex 32 > secrets/app_db_password.txt
chmod 444 secrets/postgres_password.txt secrets/app_db_password.txt

db/init-app.sh

#!/bin/sh
set -eu
app_password=$(cat /run/secrets/app_db_password)
export app_password
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" --set=ON_ERROR_STOP=1 <<'SQL'
\getenv app_password app_password
CREATE ROLE demo_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD :'app_password';
REVOKE ALL ON DATABASE demo FROM PUBLIC;
GRANT CONNECT ON DATABASE demo TO demo_app;
SQL
unset app_password

PostgreSQL ilk boş veri dizininde bu scripti çalıştırır. demo_app süper kullanıcı değildir. Uygulama bu hesapla demo veritabanında sabit SELECT sorgusunu çalıştırır. Tablo kullanan gerçek uygulamada gerekli şema ve tablo yetkilerini ayrıca verin. Mevcut volume üzerinde init scripti yeniden çalışmaz; parola dosyasını değiştirmek mevcut veritabanı parolasını döndürmez.

nginx.conf

user nginx;
worker_processes 1;
error_log /dev/stderr warn;
pid /var/run/nginx.pid;
events { worker_connections 256; }
http {
  access_log /dev/stdout;
  server {
    listen 80;
    server_name _;
    resolver 127.0.0.11 valid=10s ipv6=off;
    resolver_timeout 5s;
    location / {
        set $app_backend app:3000;
        proxy_pass http://$app_backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_read_timeout 15s;
    }
  }
}

Nginx, Docker’ın dahili DNS adresi üzerinden app adını yeniden çözer; uygulama konteyneri yeniden oluşturulduğunda eski IP’ye kalıcı olarak bağlı kalmaz. Bu tam Nginx yapılandırması tek worker kullanır ve yalnız HTTP içindir.

compose.yaml

name: docker-vps-demo
x-logging: &bounded-logs
  driver: local
  options:
    max-size: "10m"
    max-file: "3"
services:
  db:
    image: postgres:17.11-bookworm
    restart: unless-stopped
    environment:
      POSTGRES_DB: demo
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
    secrets:
      - postgres_password
      - app_db_password
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./db/init-app.sh:/docker-entrypoint-initdb.d/init-app.sh:ro
    networks: [backend]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres -d demo"]
      interval: 5s
      timeout: 3s
      retries: 12
      start_period: 20s
    mem_limit: 512m
    cpus: 0.75
    pids_limit: 200
    logging: *bounded-logs
  app:
    build: ./app
    image: docker-vps-demo:1.0
    restart: unless-stopped
    environment:
      DB_HOST: db
      DB_PASSWORD_FILE: /run/secrets/app_db_password
    secrets: [app_db_password]
    networks: [frontend, backend]
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/readyz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s
    read_only: true
    tmpfs: ["/tmp:rw,noexec,nosuid,size=16m"]
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    mem_limit: 256m
    cpus: 0.50
    pids_limit: 100
    logging: *bounded-logs
  web:
    image: nginx:1.30.6
    restart: unless-stopped
    ports: ["127.0.0.1:8080:80"]
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    networks: [frontend]
    depends_on:
      app:
        condition: service_healthy
    mem_limit: 128m
    cpus: 0.25
    pids_limit: 100
    logging: *bounded-logs
networks:
  frontend:
  backend:
    internal: true
volumes:
  db_data:
secrets:
  postgres_password:
    file: ./secrets/postgres_password.txt
  app_db_password:
    file: ./secrets/app_db_password.txt

db/init-app.sh dosyası çalıştırılabilir script olarak image entrypoint tarafından başlatılır. Proje kökünden başlatın:

chmod 755 db/init-app.sh
sudo docker compose config --quiet
sudo docker compose up -d --build --wait --wait-timeout 180
sudo docker compose ps
curl --fail http://127.0.0.1:8080/
curl --fail http://127.0.0.1:8080/db-time

chmod init scriptini çalıştırılabilir yapar; docker compose config --quiet YAML’i çözümler. up --wait konteynerlerin çalışmasını veya tanımlı healthcheck’lerin sağlıklı olmasını bekler. Son iki curl yanıtı HTTP ve uygulama-veritabanı yolunu doğrulamak içindir. Başlangıç hatasında sudo docker compose logs --tail=100 db app web ile nedenini inceleyin; veri volume’ünü silerek hatayı gizlemeyin.

  • Yalnız web servisi hosta 127.0.0.1:8080 yayımlanır. app:3000 ve db:5432 için ports tanımı yoktur.
  • web ve app frontend ağında; app ve db backend ağındadır. backend için gerçek internal: true kullanılır. Yalnız ağın adını internal yapmak bu etkiyi sağlamaz.
  • PostgreSQL 17 verisi db_data:/var/lib/postgresql/data volume’ündedir. Süper kullanıcı parolası uygulama servisine bağlanmaz.
  • DB healthcheck TCP bağlantı kabulünü, uygulama readyz kontrolü kimlik doğrulamalı sorguyu denetler. depends_on başlangıç sırasını düzenler; sonradan unhealthy olan süreci kendiliğinden yeniden başlatmaz.
  • Aynı hostun Docker bridge trafiği burada TLS ile şifrelenmez. Farklı hostlara ayrılınca ağ erişimi ve veritabanı TLS doğrulaması ayrıca kurulmalıdır.

Bu tarz çoklu servis kurulumlarını WordPress için de kullanmak istiyorsanız, hem Compose hem de kalıcı depolama tarafını detaylı anlattığımız Docker Compose ile WordPress + Nginx + MariaDB + Redis rehberimize göz atmanız faydalı olacaktır.

Adım 6: Veri Saklama ve Kalıcı Hacimler (Volumes)

Konteyneri durdurmak yazılabilir katmanını silmez; konteyner kaldırılınca bu katmandaki veri kaybolur. Kalıcı veriyi bind mount veya named volume içinde tutun. İmaj indirmek veya konteyneri yeniden oluşturmak veriyi yedeklemek değildir; volume de aynı VPS diskinin kaybına karşı tek başına koruma sağlamaz.

Named Volume ve Bind Mount Arasındaki Fark

sudo docker compose exec -T db psql -U postgres -d demo -c '\du demo_app'
sudo docker compose exec -T db psql -U postgres -d demo -c 'SELECT current_database();'
sudo docker volume inspect docker-vps-demo_db_data

Bu projede Docker named volume’ü yönetir. Bind mount ise hostta sizin seçtiğiniz bir yolu bağlar; izinleri ve dizin düzeni size aittir. Örneğin Nginx yapılandırması salt okunur bind mount kullanır. Veritabanında bind mount seçerseniz doğru PostgreSQL sürümünün veri yolunu, kullanıcı izinlerini ve yedekleme düzenini hazırlayın; çalışan veritabanı dizinini sıradan dosya kopyasıyla yedeklemeyin.

Kalıcı Veri, Yedek ve Geri Yükleme Denemesi

Proje kökünde PostgreSQL’in tutarlı mantıksal yedeğini üretin. Arşiv listesi kontrolü yedeğin gerçekten geri yüklendiğini kanıtlamaz.

umask 077
mkdir -p backups
backup_file="backups/demo-$(date -u +%Y%m%dT%H%M%SZ).dump"
if sudo docker compose exec -T db pg_dump -U postgres -d demo -Fc > "$backup_file.tmp"; then
  mv "$backup_file.tmp" "$backup_file"
  sudo docker compose exec -T db pg_restore --list < "$backup_file"
else
  rm -f "$backup_file.tmp"
  exit 1
fi

Aynı shell oturumunda, bu alıştırmada daha önce oluşturulmamış demo_restore_test adlı ayrı veritabanına geri yükleme denemesi:

sudo docker compose exec -T db createdb -U postgres demo_restore_test &&
  sudo docker compose exec -T db pg_restore -U postgres -d demo_restore_test --exit-on-error < "$backup_file" &&
  sudo docker compose exec -T db psql -U postgres -d demo_restore_test -c 'SELECT current_database();'

Bu örneğin veritabanında uygulama tablosu yoktur; gerçek projede tablo sayısı, örnek kayıtlar, yetkiler ve uygulama sorgularını da doğrulayın. pg_dump rol tanımlarını yedeklemez: burada rol init scriptiyle oluşturulur. Gerçek roller, sırlar ve kurtarma önkoşulları ayrıca korunmalıdır. Bu yerel deneme host kaybını kapsamaz; izole başka ortamda tam kurtarma tatbikatı yapın.

PostgreSQL 17 imajının veri hedefi /var/lib/postgresql/data’dır; PostgreSQL 18+ dizin düzeni farklıdır. Major sürümü yalnız image etiketini değiştirerek yükseltmeyin. Normal docker compose down named volume’ü korur; down --volumes veriyi silebilir. Volume adları Compose proje adına bağlıdır. MySQL/MariaDB yedekleme rehberimize de göz atabilirsiniz.

Adım 7: Güvenlik – İzole Uygulama Barındırmanın Temel Kuralları

Paylaşılan çekirdekte güvenlik; daemon erişimi, uygulama yetkileri, ağ ve host yamalarının birlikte yönetilmesine bağlıdır. Docker socket’ini uygulama konteynerine bağlamayın ve kimlik doğrulamasız uzak Docker API açmayın. Docker’ı yönetme yetkisi host üzerinde çok geniş yetki verir.

1. VPS Güvenlik Duvarını Doğru Yapılandırın

Bu rehberin demo portları loopback’tedir. İnternete yayın için genel akış şöyledir:

  • Bir alan adını sunucuya yönlendirin; geçerli sertifikayı alan ve yenileyen HTTPS reverse proxy kurun. Yalnız 443 eşlemek TLS oluşturmaz.
  • Dışarıdan yalnız gereken web portlarına erişim verin; SSH’ı doğrulanmış yönetici erişimiyle sınırlandırın. Yönetim erişimini kaybetmeden test edin.
  • Tek hostta ortak 80/443 girişini bir proxy yönetebilir. Her proje için aynı host 80/443 portlarını tekrar yayımlamayın.
  • Demoyu uzaktan görmek için bilgisayarınızda SSH tüneli açıp yerel http://127.0.0.1:8080 adresini ziyaret edin. Komuttaki kullanıcı ve hostu gerçek değerlerle değiştirin.
ssh -L 8080:127.0.0.1:8080 your-user@your-vps

Docker’ın yayımladığı portlar UFW’nin INPUT/OUTPUT kurallarından önce yönlendirilebilir. Bu yüzden yalnız ufw allow/deny sonucuna güvenmeyin: seçilen Docker firewall backend’ine uygun filtreleri uygulayıp dış bir hosttan IPv4 ve IPv6 erişimini sınayın. Gerekli olmayan veritabanı portlarını yayımlamayın. VPS güvenlik duvarı yapılandırma rehberimizde temel host kurallarını bulabilirsiniz.

2. Root Olarak Çalışan Konteynerlerden Kaçının

Örnekte Node uygulaması USER node ile çalışır ve tüm Linux capability’leri kaldırılır. Rootless Docker daemon ise farklı bir konudur. Nginx/PostgreSQL gibi hazır imajlarda kullanıcıyı rastgele değiştirmek başlangıçtaki dosya izinleri ve veri hazırlığını bozabilir.

  • İmajın belgelediği kullanıcıyı ve gerekli dosya izinlerini seçin; yalnız ihtiyaç duyulan yollar yazılabilir olsun.
  • Uygulama buna uygunsa read_only, tmpfs, cap_drop ve no-new-privileges kullanın; her imajda test edin.
  • privileged, host PID/ağ paylaşımı ve Docker socket bağlama gibi sınırları zayıflatan seçenekleri olağan uygulama kurulumuna eklemeyin.

3. Ortam Değişkenleri ve Secrets Yönetimi

Parolaları Compose YAML, Dockerfile, imaj katmanı veya Git içine koymayın. Örnekte app yalnız kendisine gereken parola dosyasını görür; PostgreSQL süper kullanıcı parolası ayrıca tutulur.

  • Yerel Compose secrets dosyaları konteynere bağlar; dosyaları kendiliğinden şifrelemez. Host izinleri, şifreli yedek ve erişim denetimi yine gerekir.
  • .env yapılandırma kolaylığı sağlar; şifreleme değildir. Sır içeren .env ve secrets dizinini Git dışında tutun. Git geçmişine girmiş sırları yalnız dosyadan silmek yeterli olmaz; döndürün.
  • SOPS/age veya merkezi secrets yöneticisiyle şifreli saklama kullanıyorsanız şifre çözme anahtarının erişimini ayrıca yönetin. Mevcut veritabanı parolasını değiştirmek hem veritabanı rolü hem istemci dosyası için planlı işlem gerektirir.

VPS üzerinde secrets yönetimini daha ileri seviyeye taşımak isterseniz, VPS’te secrets yönetimi rehberimizde pratik bir yol haritası bulabilirsiniz.

4. Host Tarafı Güvenliği İhmal Etmeyin

Host ele geçirilirse aynı hosttaki konteynerler, volume’ler ve sırlar tehlikeye girer. Ortak çekirdek nedeniyle host yaması ve yönetici hesapları uygulama ayrımının temelidir:

  • SSH anahtarıyla ikinci oturumda erişimi doğrulayın; ardından parola giriş politikası ve yönetici IP erişimini düzenleyin.
  • Brute-force sınırlaması yardımcı olabilir; güvenlik açığını veya çalınmış anahtarı çözmez.
  • Çekirdek, Docker ve sistem paketlerini planlı bakımda güncelleyin; gereken yeniden başlatmayı uygulayın.
  • Gereksiz host servislerini kapatın; birbirine güvenmeyen kiracıları farklı güvenlik sınırlarında barındırın.

Bu konuları daha bütüncül görmek isterseniz, VPS sunucu güvenliği rehberimizi baştan sona okumanızı öneririm.

Adım 8: İzleme, Loglar ve Sorun Giderme

Bir uygulamayı konteynerleştirdikten sonra, işin önemli kısmı başlar: izlemek ve sorun çıktığında hızlı teşhis etmek. Docker tarafında temel log ve metriklere erişmek için:

sudo docker compose ps
sudo docker compose logs --tail=100 app db web
sudo docker stats --no-stream
sudo docker system df

Servis sağlığını, yeniden başlama sayısını, CPU/belleği, disk/inode doluluğunu, log büyümesini ve uygulama hata oranını birlikte izleyin. logs yalnız stdout/stderr’e gönderilen ve log sürücüsünün sakladığı çıktıları gösterir. local log sürücüsündeki döndürme sınırları disk kullanımını azaltır; yedek veya uzun süreli denetim arşivi değildir.

Host metrikleri için Prometheus ve Node Exporter, görselleştirme için Grafana; log toplama için Loki ile Grafana Alloy değerlendirilebilir. Promtail 2 Mart 2026’da kullanım ömrünü tamamladı; yeni kurulum önerisi değildir. İzleme uçlarını ve yönetim panellerini herkese açık bırakmayın. VPS izleme ve alarm kurulum rehberimiz bu bileşenleri planlamak için başlangıç sağlar.

Adım 9: Yedekleme ve Güncelleme Stratejisi

Docker ile VPS’te izole barındırma kurulduktan sonra, sürdürülebilirlik için iki kritik alana odaklanmalısınız: yedekler ve güncellemeler.

Yedekleme

  • Dockerfile, kilit, Compose ve sır içermeyen yapılandırmayı Git’te koruyun. Parolaları ve şifre çözme anahtarlarını aynı depoya eklemeyin.
  • Canlı veritabanı için pg_dump veya veritabanının desteklediği tutarlı fiziksel yedek yöntemini kullanın. rsync/restic tek başına çalışan veri dizinini tutarlı hale getirmez.
  • Yüklenen dosyaları uygulama tutarlılığına uygun biçimde yedekleyin; yedekleri şifreleyip hosttan bağımsız bir konuma kopyalayın.
  • Saklama süresi, yedek yaşı ve kurtarma süresi hedeflerini belirleyin; başka bir ortamda restore ve uygulama kontrolü yapın.

Güncellemeler

  • Base imajları ve uygulama bağımlılıklarını test ortamında yenileyin; pg ve package-lock.json değişikliklerini birlikte inceleyin.
  • Yeni uygulama yayınına yeni sürüm etiketi verin; önceki test edilmiş imajı ve digest bilgisini geri dönüş için saklayın.
  • Compose pull, build ile üretilen yerel uygulamayı yeniden derlemez. Aşağıdaki akış db/web imajlarını çeker, app’i yeniden build eder ve değişen servisleri yeniden oluşturur.
  • Tek sunucuda Compose up kesintisiz rolling deployment garantilemez. Bakım penceresi ve şema/veri uyumlu geri dönüş planı hazırlayın.
sudo docker compose pull db web
sudo docker compose build --pull app
sudo docker compose up -d --wait --wait-timeout 180
sudo docker compose ps
curl --fail http://127.0.0.1:8080/db-time

WordPress gibi popüler uygulamaları Docker ile yönetirken yedek ve güncelleme süreçlerini nasıl kurgulayacağınızı merak ediyorsanız, Docker ile WordPress’i VPS’te yaşatma rehberimizde uçtan uca bir senaryo bulabilirsiniz.

Tipik Senaryo: Tek VPS’te Birden Fazla Uygulamayı İzole Etmek

Aynı yöneticiye ait kurumsal site, API ve yardımcı servisler bir VPS üzerinde çalışabilir. Her birinin kaynak, veri ve güvenlik ihtiyacı farklıdır; örnek bileşenler:

  • Kurumsal web siteniz (PHP/Laravel)
  • Basit bir Node.js API
  • Redis cache
  • PostgreSQL veritabanı

Bunu aşağıdaki gibi bir mimari ile yönetebilirsiniz:

  • Her mantıksal proje için ayrı Compose proje adı, ağlar ve volume’ler kullanın. Ortak volume veya ağla istemeden veri/erişim paylaşmayın.
  • Tek bir HTTPS giriş proxy’siyle alan adlarını doğru uygulamaya yönlendirin; sertifika yenilemesini ve proxy güven politikasını ayrıca kurun.
  • İkinci proje örneğini kopyalarken yalnız dizin değil Compose adı, imaj etiketi ve host portunu da değiştirin. docker-vps-demo_db_data mevcut projenin verisidir.
  • Kaynak limitleri ve disk/yedek bütçesini bütün VPS için hesaplayın. Daha güçlü ayrım veya bağımsız arıza alanı gerekiyorsa ayrı VPS/VM kullanın.

Böylece bağımlılıkları ve servis yaşam döngüsünü daha düzenli yönetebilirsiniz. Yine de aynı hostun disk, çekirdek ve kullanılabilirlik riskleri ortaktır; mimari seçimini yalnız konteyner sayısıyla yapmayın.

Sonuç: Docker + VPS Kombinasyonuyla Esnek ve İzole Bir Platform Kurmak

Docker ile VPS üzerinde uygulama çalıştırmanın değeri; sürümlenen imaj, tanımlı ağ, açık veri yolu ve denetlenebilir işletim adımlarından gelir. Örnek, tek bir uygulamanın konteynerleştirilmesinden veritabanı sorgusu yapan üç servisli yerel HTTP düzenine kadar ilerler.

İnternete açmadan önce alan adı/TLS, dış erişim testi, yedek ve farklı ortamda geri yükleme tamamlanmalıdır. Değişiklik sonrası HTTP yanıtını, veritabanı erişimini, logları ve kaynakları kontrol edin. Başarılı YAML çözümlemesi tek başına üretim doğrulaması değildir.

VPS seçimini ölçülmüş CPU, RAM, disk ve trafik gereksinimleriyle yapın. Önce tek bir uygulamada bu akışı oturtun; kapasite veya güvenlik sınırı gerektirdiğinde servisleri ayrı sunuculara ayırın. Aynı Compose dosyası taşınsa bile veriler, sırlar, DNS ve ağ erişimi için ayrı geçiş planı gerekir.

Birincil Teknik Kaynaklar

Sıkça Sorulan Sorular

Evrensel bir minimum vCPU/RAM değeri veya konteyner sayısı yoktur. Uygulama, veritabanı, worker, derleme, log ve yedek yükünü birlikte ölçün; host için pay bırakın. Yazıdaki bellek/CPU/PID değerleri yalnız örnek sınırlardır. Disk kapasitesi ve I/O ihtiyacı veri büyümesine bağlıdır; NVMe etiketi tek başına belirli uygulama performansı garanti etmez.

Konteynerler süreç, dosya ve ağ alanlarını ayırabilir, fakat host çekirdeği ortaktır. Root olmayan uygulama, sınırlı capability, kapalı Docker API ve host yamaları riski azaltır; VM düzeyinde ayrı çekirdek oluşturmaz. Birbirine güvenmeyen kiracılar veya bağımsız arıza alanı gerektiren işler için ayrı VPS/VM gibi daha güçlü sınırlar değerlendirilmelidir.

Aynı CPU, disk ve çekirdek paylaşıldığından bir servisin yükü diğerlerini etkileyebilir. Compose mem_limit, cpus ve pids_limit ile örnek bütçeler uygular; log döndürme ve disk izleme de gerekir. Limitler kapasite garantisi değildir. Temsilî yükte gecikme, hata, CPU/bellek ve I/O ölçün; gerekirse uygulamaları ayrı sunuculara ayırın.

Hayır. Sürüm kontrollü uygulama, bağımlılık kilidi, Dockerfile ve sır içermeyen Compose/proxy dosyaları da gerekir. Canlı veritabanı için tutarlı pg_dump veya desteklenen fiziksel yedek kullanın; sıradan volume kopyası yeterli değildir. Sırları ve kurtarma anahtarlarını ayrı koruyun. Yedekleri hosttan bağımsız depolayın ve izole ortamda veriler, yetkiler ve uygulama sorgularıyla geri yüklemeyi doğrulayın.

Önce tek bir uygulamada Dockerfile, kilit, ağ ve veri yolunu öğrenin. Bu rehberin loopback örneğini test ortamında kurup HTTP yanıtı, veritabanı erişimi, loglar ve geri yüklemeyi kontrol edin. Canlı geçişten önce alan adı/TLS, dış erişim ve veri/şema uyumlu geri dönüş planını tamamlayın. Compose dosyasını taşımak sırları ve verileri kendiliğinden taşımaz.
↑