Volume, Bind Mount ve tmpfs: Container'da Veriyi Kalıcı Tutmak
Container'ın yazılabilir layer'ı container silinince gider; kalıcı olması gereken veri named volume'de, host'tan verilen dosyalar bind mount'ta, geçici veri tmpfs'te durmalı. BookstoreDb'yi SQL Server 2025 container'ında volume ile çalıştırıp container'ı silip veriyi korumayı, BACKUP DATABASE ve tar ile yedeklemeyi, non-root SQL Server'ın bind mount'ta aldığı Permission denied hatasını, testler için tmpfs'i ve anonymous volume tuzağını gerçek çıktılarla görüyoruz.
Sürüm notu: Bu yazıdaki örnekler Docker Engine 29.9.3, SQL Server 2025 (RTM-CU11, mcr.microsoft.com/mssql/server:2025-latest), alpine:3 ve nginxinc/nginx-unprivileged:1.30-alpine ile denenmiştir.
Önce Tahmin Et
SQL Server container'ını hiçbir -v bayrağı vermeden başlattın, içinde BookstoreDb'yi oluşturup altı kitap ekledin. Sonra docker rm -f ile container'ı silip aynı docker run komutunu tekrar çalıştırdın. Veritabanı yerinde mi? İkinci soru: docker run --rm -v /data alpine:3 ... komutunu yüz kez çalıştırırsan diskte kaç volume birikir?
Container'ın Yazılabilir Layer'ı Geçicidir
4. yazıda image'ın salt okunur layer'lardan oluştuğunu, container'ın bunların üstüne kendine ait ince bir yazılabilir layer eklediğini görmüştük. Container'ın içinde yazılan her dosya bu layer'a gider ve layer container'la birlikte yaşar: docker stop ve docker start arasında durur, docker rm ile silinir. Uygulama kodu için istediğimiz davranış bu; veritabanı için felaket.
Bu yazıda sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) veritabanı BookstoreDb'yi SQL Server 2025 container'ında çalıştırıyoruz. Önce image'ın kendisi veri için bir yer ayırıyor mu, ona baktık:
docker image inspect mcr.microsoft.com/mssql/server:2025-latest \
--format 'User={{.Config.User}} Volumes={{json .Config.Volumes}}'
User=mssql Volumes=null
Image'da VOLUME tanımı yok. Yani -v vermezsen SQL Server'ın /var/opt/mssql altındaki bütün veri ve log dosyaları container'ın yazılabilir layer'ına yazılır. Örneklerdeki komutlar Windows'ta WSL (Windows Subsystem for Linux; Windows'ta Linux çalıştıran alt sistem) 2 üzerindeki bash için; SA (SQL Server'ın yerleşik yönetici hesabı) şifresini önce shell'de export MSSQL_SA_PASSWORD=... ile tanımladık ve -e MSSQL_SA_PASSWORD ile değer yazmadan geçtik. Docker değeri shell'den alır, şifre komut geçmişine girmez. Schema'yı ve örnek kitapları db/01-bookstore.sql script'i oluşturuyor; script'i container'a salt okunur bir bind mount ile veriyoruz (birazdan ayrıntısı):
docker run -d --name dockerseries-db -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD \
-v "$PWD/db:/scripts:ro" mcr.microsoft.com/mssql/server:2025-latest
docker exec -e SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" dockerseries-db \
/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -i /scripts/01-bookstore.sql
Changed database context to 'BookstoreDb'.
(6 rows affected)
sqlcmd şifreyi SQLCMDPASSWORD ortam değişkeninden okuyor, -P ile komut satırına yazmaya gerek yok. Container'ı silip aynı komutla yeniden açtık ve kullanıcı veritabanlarını listeledik:
docker rm -f dockerseries-db
docker run -d --name dockerseries-db -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD \
-v "$PWD/db:/scripts:ro" mcr.microsoft.com/mssql/server:2025-latest
docker exec -e SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" dockerseries-db \
/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -h -1 \
-Q "SET NOCOUNT ON; SELECT name FROM sys.databases WHERE database_id > 4"
Çıktı boş. İlk sorunun cevabı: veritabanı yok, çünkü yeni container yeni bir yazılabilir layer'la başladı. Log'da da SQL Server'ın sistem veritabanlarını sıfırdan kurduğunu görürsün. Container'ın ömründen bağımsız yaşaması gereken her veri container'ın dışında, bir mount'ta durmalı.
Named Volume ile BookstoreDb
Docker'ın bu iş için sunduğu yol named volume: Docker'ın kendisinin oluşturduğu, adı olan ve container silinince silinmeyen bir dizin. SQL Server'ın bütün dosyalarını tutan /var/opt/mssql'i bir volume'e bağladık:
docker volume create dockerseries-sqldata
docker run -d --name dockerseries-db -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD \
-p 127.0.0.1:1433:1433 \
-v dockerseries-sqldata:/var/opt/mssql \
-v "$PWD/db:/scripts:ro" \
mcr.microsoft.com/mssql/server:2025-latest
-v'nin sol tarafı / ya da . ile başlamıyorsa Docker onu volume adı olarak yorumlar; başlıyorsa host'taki bir yol, yani bind mount. docker volume create zorunlu değil, docker run olmayan bir volume'ü kendisi oluşturur, ama ayrı komut volume'ün bilinçli bir kaynak olduğunu açıkça gösteriyor. Port'u 127.0.0.1'e yayımladık; SQL Server'a host'taki araçlarla (ör. Visual Studio Code'un SQL Server eklentisi) yalnız bu makineden bağlanılsın, ağdaki başka makineler 1433'ü görmesin. Neden önemli olduğunu 12. yazıda göreceğiz.
Script'i çalıştırdık, üstüne çalışma anında bir kitap daha ekledik, sonra container'ı silip aynı komutla yeniden oluşturduk:
docker exec -e SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" dockerseries-db \
/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -d BookstoreDb \
-Q "INSERT INTO dbo.Books VALUES ('9780547928227', N'The Hobbit', N'J. R. R. Tolkien', 260.00)"
docker rm -f dockerseries-db
# ... aynı docker run komutu ...
docker exec -e SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" dockerseries-db \
/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -d BookstoreDb \
-Q "SET NOCOUNT ON; SELECT COUNT(*) AS Books FROM dbo.Books"
Books
-----------
7
Yedi kitap: script'in eklediği altısı ve sonradan eklenen The Hobbit. Container gitti, veri kaldı. SQL Server'ı yeni bir CU'ya (Cumulative Update; SQL Server'ın toplu güncelleme paketi) yükseltmek de aynı yoldan yapılır: yeni image'la yeni container, aynı volume. Bu yüzden script'i tekrar çalıştırılabilir yazdık; veritabanı ve tablo yoksa oluşturuyor, kitapları MERGE ile yalnız eksikse ekliyor:
IF DB_ID(N'BookstoreDb') IS NULL
CREATE DATABASE BookstoreDb;
GO
USE BookstoreDb;
GO
IF OBJECT_ID(N'dbo.Books', N'U') IS NULL
CREATE TABLE dbo.Books
(
Isbn char(13) NOT NULL CONSTRAINT PK_Books PRIMARY KEY,
Title nvarchar(200) NOT NULL,
Author nvarchar(200) NOT NULL,
PriceTry decimal(10,2) NOT NULL
);
GO
MERGE dbo.Books AS target
USING (VALUES
('9780441172719', N'Dune', N'Frank Herbert', 420.00),
('9789750719387', N'İnce Memed', N'Yaşar Kemal', 310.00),
('9789750738609', N'Çalıkuşu', N'Reşat Nuri Güntekin', 240.00),
('9789754700114', N'Saatleri Ayarlama Enstitüsü', N'Ahmet Hamdi Tanpınar', 280.00),
('9780141439587', N'Emma', N'Jane Austen', 180.00),
('9789750722752', N'Kürk Mantolu Madonna', N'Sabahattin Ali', 150.00)
) AS source (Isbn, Title, Author, PriceTry)
ON target.Isbn = source.Isbn
WHEN NOT MATCHED THEN
INSERT (Isbn, Title, Author, PriceTry) VALUES (source.Isbn, source.Title, source.Author, source.PriceTry);
GO
Log satırlarından biri image hakkında bir şey daha söylüyor: MSSQL_PID verilmediğinde SQL Server 2025 container'ı "Enterprise Developer Edition" olarak başlıyor. Developer edition'lar yalnız geliştirme ve test içindir; production lisansı ayrı bir konu.
Üç Mount Tipi: Volume, Bind Mount ve tmpfs
Container'ın dosya sistemine dışarıdan bir şey bağlamanın üç yolu var ve yukarıdaki komut ikisini birlikte kullanıyor:
| Tip | Veri nerede? | Kim yönetir? | Tipik kullanım |
|---|---|---|---|
| Named volume | Engine'in kendi alanı (/var/lib/docker/volumes/) | Docker (docker volume komutları) | Veritabanı dosyaları, kalıcı uygulama verisi |
| Bind mount | Host'ta senin seçtiğin bir yol | Sen (host'un dosya sistemi ve izinleri) | Kaynak kod, config ve script'leri container'a vermek |
| tmpfs | Host'un belleği | Kimse; container durunca silinir | Geçici dosyalar, testlerin atılabilir verisi |
db/ klasörü bind mount, çünkü dosyanın asıl yeri repository ve onu editörde değiştirdiğinde container'ın hemen görmesini istiyoruz. Sonundaki :ro, container'ın bu dosyaları değiştirememesi için; container'a verdiğin config ve script'leri her zaman salt okunur bağla. Veritabanı dosyaları ise volume'de, çünkü onların host'ta belirli bir yerde durmasına ihtiyacımız yok ve izinlerini Docker'ın yönetmesi işimizi kolaylaştırıyor. docker inspect ikisini ayırıyor:
docker inspect dockerseries-db \
--format '{{range .Mounts}}{{.Type}} {{.Name}}{{.Source}} -> {{.Destination}} rw={{.RW}}{{println}}{{end}}'
bind /home/user/bookstore/db -> /scripts rw=false
volume dockerseries-sqldata/var/lib/docker/volumes/dockerseries-sqldata/_data -> /var/opt/mssql rw=true
Aynı şeyi --mount ile de yazabilirsin: --mount type=volume,source=dockerseries-sqldata,target=/var/opt/mssql. Daha uzun ama her alanı adıyla yazdığı için okunması kolay ve bir farkı var: bind mount'ta host'taki yol yoksa -v o yolu root'a ait boş bir klasör olarak sessizce oluşturur, --mount ise hata verir:
docker run --rm --mount type=bind,source=/srv/missing,target=/data alpine:3 true
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /srv/missing
Yolu yanlış yazdığında boş bir klasörle çalışan bir container yerine hemen hata almak çoğu zaman daha iyi; script'lerde ve dokümantasyonda --mount'u tercih etmenin nedeni bu.
Volume'leri Yönetmek
docker volume ls --filter name=dockerseries
docker volume inspect dockerseries-sqldata
docker system df -v | grep -A3 "^VOLUME NAME"
DRIVER VOLUME NAME
local dockerseries-sqldata
[
{
"CreatedAt": "2026-11-14T12:15:19Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/dockerseries-sqldata/_data",
"Name": "dockerseries-sqldata",
"Options": null,
"Scope": "local"
}
]
VOLUME NAME LINKS SIZE
dockerseries-sqldata 1 152.2MB
Mountpoint, engine'in çalıştığı makinedeki yol. Linux sunucuda doğrudan o klasördür; Docker Desktop'ta engine WSL 2'deki Linux VM'inde çalıştığı için yol o VM'in içinde. Volume'ün içine bakmak için bu yolu aramak yerine volume'ü geçici bir container'a bağlamak her ortamda çalışır. LINKS, volume'ü kullanan container sayısı. Boş bir SQL Server kurulumu ve küçük bir veritabanı bile 152 MB tutuyor; sistem veritabanları ve log dosyaları baştan yer ayırıyor.
Temizlik komutu docker volume prune. Engine 23.0'dan beri varsayılan olarak yalnız hiçbir container'ın kullanmadığı anonymous volume'leri (adı olmayan, Docker'ın hash ile adlandırdığı volume'ler) siler; kullanılmayan named volume'leri de silmesi için --all gerekir. Yani eski bir blog yazısındaki docker volume prune komutu bugün farklı davranır. Bu seride paylaşılan makinelerde prune çalıştırmıyoruz; volume'leri adıyla, docker volume rm dockerseries-sqldata ile siliyoruz. Silmeden önce ne sildiğini bilmek, veritabanı söz konusu olduğunda tek güvenli yol.
Yedekleme ve Geri Yükleme
Volume container'ın silinmesine dayanır ama diskin bozulmasına, yanlış bir rm'e ya da hatalı bir migration'a dayanmaz. İki yol denedik. Birincisi veritabanının kendi yedeği; SQL Server çalışırken tutarlı bir yedek alır:
docker exec -e SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" dockerseries-db \
/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa \
-Q "BACKUP DATABASE BookstoreDb TO DISK = N'/var/opt/mssql/backup/BookstoreDb.bak' WITH INIT, COMPRESSION"
mkdir -p backup
docker cp dockerseries-db:/var/opt/mssql/backup/BookstoreDb.bak backup/
Processed 416 pages for database 'BookstoreDb', file 'BookstoreDb' on file 1.
Processed 2 pages for database 'BookstoreDb', file 'BookstoreDb_log' on file 1.
BACKUP DATABASE successfully processed 418 pages in 0.027 seconds (120.804 MB/sec).
İkincisi volume'ün tamamını dosya olarak almak. docker CLI'da volume yedeği için ayrı bir komut yok; yöntem, volume'ü ve host'taki bir klasörü küçük bir container'a bağlayıp tar çalıştırmak. Veritabanı dosyalarını çalışırken kopyalamak tutarsız bir yedek üretebileceği için önce container'ı durdurduk:
docker stop dockerseries-db
docker run --rm -v dockerseries-sqldata:/data:ro -v "$PWD/backup:/backup" alpine:3 \
tar czf /backup/sqldata.tar.gz -C /data .
ls -lh backup
-rw-rw---- 1 root root 516.0K Nov 14 12:15 BookstoreDb.bak
-rw-r--r-- 1 root root 7.4M Nov 14 12:15 sqldata.tar.gz
Geri yüklemek tersini yapmak: yeni bir volume, içine tar xzf, sonra SQL Server'ı o volume'le başlatmak:
docker volume create dockerseries-sqldata-restored
docker run --rm -v dockerseries-sqldata-restored:/data -v "$PWD/backup:/backup:ro" alpine:3 \
sh -c "tar xzf /backup/sqldata.tar.gz -C /data && ls -ln /data"
docker rm dockerseries-db
docker run -d --name dockerseries-db -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD \
-v dockerseries-sqldata-restored:/var/opt/mssql mcr.microsoft.com/mssql/server:2025-latest
# ... SELECT COUNT(*) FROM dbo.Books ...
drwxr-xr-x 2 10001 10001 4096 Nov 14 12:15 backup
drwxr-xr-x 2 10001 10001 4096 Nov 14 12:15 data
drwxr-xr-x 2 10001 10001 4096 Nov 14 12:15 log
drwxr-xr-x 2 10001 10001 4096 Nov 14 12:15 secrets
Books
-----------
7
Yedi kitap geri geldi. tar root olarak çalıştığı için dosyaların sahibi olan uid 10001'i koruyor; bu ayrıntı bir sonraki bölümde önemli. İki yöntemin farkı kapsamda: .bak tek bir veritabanının yedeği, başka bir SQL Server'a (container olsun olmasın) RESTORE edilebilir ve çalışan sistemi durdurmayı gerektirmez. Volume tar'ı ise sunucunun bütün durumunu (sistem veritabanları, login'ler, ayarlar) taşır ama container'ın durmasını ister. Production'da yedeğin asıl yolu veritabanının kendi yedek mekanizması; yedek stratejisi ve RESTORE ayrıntıları SQL Server Derinlemesine serisinin konusu.
Permission'lar: Non-root SQL Server ve Bind Mount
SQL Server 2019'dan beri container image'ları root olmayan mssql kullanıcısıyla başlıyor; container'da id komutu uid=10001(mssql) gösteriyor. Named volume'de bu kullanıcı hiç sorun çıkarmadı, çünkü boş bir volume ilk bağlandığında Docker image'daki /var/opt/mssql klasörünün içeriğini, sahibini ve izinlerini volume'e kopyalıyor. Image'da bu klasör root'a ait ama grubu mssql (gid 10001) ve grubun yazma izni var (drwxrwx---). Bind mount'ta böyle bir kopyalama yok: container host'taki klasörü olduğu gibi, host'taki sahibi ve izinleriyle görür. Linux bir sunucuda veritabanı dosyalarını /srv altında tutmak isteyelim:
sudo mkdir -p /srv/bookstore/mssql
docker run -d --name dockerseries-db -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD \
-v /srv/bookstore/mssql:/var/opt/mssql mcr.microsoft.com/mssql/server:2025-latest
docker ps -a --filter name=dockerseries-db --format "{{.Names}}: {{.Status}}"
docker logs dockerseries-db 2>&1 | tail -1
dockerseries-db: Exited (1) 14 seconds ago
/opt/mssql/bin/sqlservr: Error: The system directory [/.system] could not be created. File: /mnt/vss/_work/1/s/sqlpal/Hosts/Linux/LinuxDirectory.cpp:420 [Status: 0xC0000022 Access denied errno = 0xD(13) Permission denied]
Klasörü sudo ile oluşturduk, sahibi root ve izinleri 755; uid 10001 oraya yazamıyor ve SQL Server ilk dosyasını oluştururken çıkıyor. Hata mesajındaki yol (/.system) SQL Server'ın Linux katmanının kendi iç yolu, kafa karıştırmasın; önemli kısım sondaki Permission denied. Microsoft'un dokümanındaki çözüm klasörün sahibini container'daki kullanıcıya vermek:
sudo chown -R 10001:0 /srv/bookstore/mssql
Aynı komutla başlatınca SQL Server açıldı ve data, log, secrets klasörlerini uid 10001 sahipliğinde oluşturdu. Host'ta bir uid'nin adı olmayabilir; ls -l o yüzden mssql yerine 10001 gösterir. Container ile host aynı kernel'i ve aynı uid numaralarını paylaşır, isimler değil numaralar eşleşir.
Windows'taki okur için bir not: Docker Desktop'ta bind mount'un hızı dosyanın nerede durduğuna bağlı. Docker'ın WSL dokümanı bind mount edilecek dosyaları Windows diskinde (/mnt/c/...) değil, WSL dağıtımının Linux dosya sisteminde (~/projects/... gibi) tutmanı öneriyor: Windows dosya sistemine her erişim VM sınırını geçer ve yavaştır, dosya değişikliği bildirimleri (hot reload'un dayandığı inotify olayları) de yalnız Linux tarafındaki dosyalar için çalışır. Veritabanı dosyalarını ise zaten bind mount'a değil volume'e koyuyoruz; volume'ler VM'in kendi diskinde durduğu için bu sorundan etkilenmez.
tmpfs: Testler İçin Atılabilir Veritabanı
tmpfs mount'u diske değil belleğe yazar ve container durduğunda içeriği silinir; yalnız Linux üzerinde çalışan engine'lerde vardır (Docker Desktop'taki engine de Linux). Kalıcılığın istenmediği bir senaryo: integration testleri her koşuda temiz bir veritabanıyla başlamak ister ve disk I/O'su testleri yavaşlatır. SQL Server'ın bütün dosya alanını tmpfs yaptık:
docker run -d --name dockerseries-db -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD \
--mount type=tmpfs,destination=/var/opt/mssql,tmpfs-size=2g,tmpfs-mode=1777 \
-v "$PWD/db:/scripts:ro" mcr.microsoft.com/mssql/server:2025-latest
# ... script'i çalıştır ...
docker exec dockerseries-db df -h /var/opt/mssql
docker restart dockerseries-db
# ... BookstoreDb var mı? ...
Filesystem Size Used Avail Use% Mounted on
tmpfs 2.0G 145M 1.9G 8% /var/opt/mssql
0
Script çalıştı, veritabanı bellekte 145 MB tuttu. docker restart sonrasında sys.databases'te BookstoreDb sayısı 0: tmpfs'in içeriği docker rm'i bile beklemeden, container her durduğunda gidiyor. tmpfs-size vermezsen sınır yok ve Docker'ın dokümanına göre pratikte host belleğinin yarısına kadar büyüyebilir; bellek limiti olan bir CI runner'ında mutlaka bir boyut ver. tmpfs-mode=1777 klasörü /tmp gibi herkese yazılabilir yapıyor; root olmayan mssql kullanıcısının yazabilmesi buna bağlı. Varsayılan değer de 1777, ama komutta açıkça yazmak bağımlılığı görünür kılıyor. Testlerde container'ın yaşam döngüsünü testin kendisinden yönetmeyi Integration ve E2E Test serisi Testcontainers ile anlatıyor; tmpfs orada da aynı bayrakla kullanılır.
Üretimde Dikkat
SQL Server container image'ları Microsoft'un dokümanına göre yalnız x86-64 (Intel/AMD) işlemcili Linux host'larda destekleniyor; Windows'ta Docker Desktop ile x64 bir makinede sorun yok. ARM (telefonlarda ve yeni sunucularda yaygın işlemci mimarisi) tabanlı sunucuya taşımayı düşünüyorsan veritabanını orada container'da çalıştırmak bir seçenek değil. SQL Server en az 2 GB bellek istiyor; Docker Desktop'ta WSL 2'ye ayrılan belleği buna göre ayarla.
Production veritabanını container'da çalıştırma kararını alışkanlıkla verme. Container'ın getirdiği kolaylık (aynı image, hızlı kurulum) veritabanında küçük; asıl iş yedek, geri yükleme testi, disk performansı, yüksek erişilebilirlik ve güncelleme planı. Bunları yönetilen bir veritabanı servisi (Azure SQL ya da Amazon RDS (Relational Database Service; Amazon'un yönetilen veritabanı hizmeti)) senin yerine yapar. Geliştirme, test, demo ve küçük iç uygulamalar için container'daki SQL Server çok iyi bir seçim. Kubernetes serisinin "Kubernetes'te Storage ve Stateful Uygulamalar: Volume, PersistentVolumeClaim, StatefulSet ve Veritabanı Kararı" yazısı aynı kararı cluster açısından tartışıyor.
Yedeği almak yetmez, geri yüklemeyi dene. Yukarıdaki gibi yedeği yeni bir volume'e açıp container'ı başlatmak ve bir sorgu çalıştırmak birkaç dakika sürüyor; ilk kez gerçek bir arızada denemekten çok daha ucuz.
Volume'lerine sahiplik bilgisi koy: docker volume create --label app=bookstore --label data=database dockerseries-sqldata gibi label'lar, birkaç ay sonra docker volume ls --filter label=data=database ile neyin silinmemesi gerektiğini bulmanı sağlar.
Sık Düşülen Tuzaklar
Anonymous volume'leri biriktirmek. İkinci sorunun cevabı için iki komut denedik:
docker run --name dockerseries-anon -v /data alpine:3 sh -c "echo order-42 > /data/note.txt"
docker rm dockerseries-anon
docker volume ls
docker run --rm -v /data alpine:3 sh -c "echo order-43 > /data/note.txt"
docker volume ls
DRIVER VOLUME NAME
local 95f2ffb717796efde96b7ac5660261a40e34c53ef3d0a8a4cf9ea25f0bc61aed
DRIVER VOLUME NAME
local 95f2ffb717796efde96b7ac5660261a40e34c53ef3d0a8a4cf9ea25f0bc61aed
(Diğer volume'leri çıkardım.) Yalnız hedef yolu verilen -v /data anonymous volume oluşturur. docker rm onu silmez (docker rm -v silerdi) ve hash adlı volume kimsenin hatırlamadığı bir veriyle diskte kalır. --rm ile çalışan container ise kendi anonymous volume'lerini de birlikte siler; ikinci komut yeni bir volume bırakmadı. Yani cevap sıfır, ama aynı komutu --rm'siz çalıştırıp container'ları docker rm ile silersen yüz. Image'ında VOLUME tanımı olan bir veritabanını (birçok resmi image böyle) -v'siz çalıştırdığında da aynısı olur: veri anonymous volume'dedir ve container'ı yeniden oluşturduğunda yeni container yeni, boş bir volume alır. Veritabanına her zaman adı olan bir volume ver.
Uygulama dosyalarının üstüne volume bağlamak. Boş bir volume image'daki içerikle doldurulur, dolu bir volume ise image'daki içeriği gizler. bookstore-web'in dosyalarını volume'e almayı denedik, sonra aynı volume'ü düz nginx image'ıyla bağladık:
docker run -d --name dockerseries-web -e API_BASE_URL=http://localhost:8080 \
-v dockerseries-webroot:/usr/share/nginx/html dockerseries-bookstore-web:1.0.0
docker run --rm -v dockerseries-webroot:/data alpine:3 ls /data
docker rm -f dockerseries-web
docker run -d --name dockerseries-web -p 8081:8080 \
-v dockerseries-webroot:/usr/share/nginx/html nginxinc/nginx-unprivileged:1.30-alpine
curl -s localhost:8081/ | grep -o "<title>.*</title>"
50x.html
config.json
favicon.ico
index.html
main-BCJ2DRL6.js
styles-5INURTSO.css
<title>BookstoreWeb</title>
Başka bir image bile bookstore-web'i sunmaya devam etti. Aynısı yeni bir sürümde olur: 1.0.1 image'ını aynı volume'le başlatırsan kullanıcılar 1.0.0'ın dosyalarını görür ve image güncellemesi hiçbir şeyi değiştirmez. Volume veri içindir; uygulamanın kendisi image'da kalmalı. Volume doldurma davranışını istemediğin durumda --mount'a volume-nocopy seçeneği eklenir.
Veriyi tek komutla silmek. docker volume prune --all, kullanılmayan bütün named volume'leri siler; veritabanı container'ın o anda durmuşsa onun volume'ü de "kullanılmayan" sayılır. Docker Compose kullanıyorsan docker compose down -v da compose dosyasındaki volume'leri siler; ayrıntısı Docker Compose serisinde. İkisini de otomatik temizlik script'lerine koymadan önce bir kez daha düşün.
Yedekleri build context'ine bırakmak. backup/ klasörünü repository'nin kökünde oluşturduk ve Bookstore.Api'nin Dockerfile'ı kökü build context olarak kullanıyor; .dockerignore'a eklemezsen her build 8 MB'lık arşivi (ve gerçek bir sistemde müşteri verisini) BuildKit'e gönderir. .dockerignore'a bir satır ekledik:
backup/
Küçük Bir Deneme
★☆☆ dockerseries-sqldata volume'ünü --label app=bookstore ile yeniden oluştur, SQL Server'ı onunla başlat ve docker volume ls --filter label=app=bookstore çıktısına bak. Container'ı silince volume listede kalıyor mu?
★★☆ BookstoreDb.bak dosyasını yeni ve boş bir volume'le başlattığın ikinci bir SQL Server container'ına docker cp ile kopyala ve RESTORE DATABASE BookstoreDb FROM DISK = ... ile geri yükle. Hangi kullanıcının dosyaya okuma izni olması gerekiyor?
★★★ Bind mount örneğini chown yerine docker run --user 10001:0 ve chmod g+rwx kombinasyonuyla çalıştırmayı dene. Microsoft'un dokümanındaki "root grubu" önerisi neden işe yarıyor?
BookstoreDb artık bir volume'de ve container'lardan bağımsız. Ama Bookstore.Api onu hâlâ göremiyor: API ayrı bir container'da ve iki container birbirini bilmiyor. Bir sonraki yazıda Docker network'lerine geçiyoruz: default bridge ile user-defined network farkı, container adıyla DNS çözümü, API'nin Server=bookstore-db ile veritabanına bağlanması ve port publishing'in güvenli varsayılanları.