{"id":3643,"date":"2025-12-29T15:24:42","date_gmt":"2025-12-29T12:24:42","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/running-isolated-docker-containers-on-a-vps-step-by-step-beginner-guide\/"},"modified":"2025-12-29T15:24:42","modified_gmt":"2025-12-29T12:24:42","slug":"running-isolated-docker-containers-on-a-vps-step-by-step-beginner-guide","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/running-isolated-docker-containers-on-a-vps-step-by-step-beginner-guide\/","title":{"rendered":"Running Isolated Docker Containers on a VPS: Step\u2011by\u2011Step Beginner Guide"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>If you are deploying multiple apps on a single <a href=\"https:\/\/www.dchost.com\/vps\">VPS<\/a>, Docker is one of the simplest ways to keep everything neat, isolated and easy to manage. Instead of manually installing PHP, Node.js, databases and tools side by side on the same operating system, you bundle each application into its own container and run them in parallel without conflicts. On a properly configured VPS from dchost.com, this gives you a clean separation between projects, faster rollbacks, simpler updates and a much more predictable environment.<\/p>\n<p>In this guide, we walk through running isolated Docker containers on a VPS from scratch, step by step. We will cover how to prepare your VPS, install Docker, run your first containers, separate projects with networks and volumes, and harden everything for real\u2011world use. The goal is a practical, copy\u2011paste\u2011friendly guide you can follow even if you are new to Linux and Docker. By the end, you will have a clear mental model of how containers work on a VPS and a repeatable workflow you can reuse for every new project.<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Why_Run_Docker_on_a_VPS_Instead_of_Installing_Everything_Directly\"><span class=\"toc_number toc_depth_1\">1<\/span> Why Run Docker on a VPS Instead of Installing Everything Directly?<\/a><ul><li><a href=\"#Containers_vs_virtual_machines_in_simple_terms\"><span class=\"toc_number toc_depth_2\">1.1<\/span> Containers vs virtual machines in simple terms<\/a><\/li><li><a href=\"#Realworld_problems_Docker_solves_on_a_VPS\"><span class=\"toc_number toc_depth_2\">1.2<\/span> Real\u2011world problems Docker solves on a VPS<\/a><\/li><\/ul><\/li><li><a href=\"#Preparing_Your_VPS_for_Docker\"><span class=\"toc_number toc_depth_1\">2<\/span> Preparing Your VPS for Docker<\/a><ul><li><a href=\"#Minimum_VPS_requirements\"><span class=\"toc_number toc_depth_2\">2.1<\/span> Minimum VPS requirements<\/a><\/li><li><a href=\"#Update_and_secure_the_base_system\"><span class=\"toc_number toc_depth_2\">2.2<\/span> Update and secure the base system<\/a><\/li><\/ul><\/li><li><a href=\"#Installing_Docker_on_Your_VPS_Step_by_Step\"><span class=\"toc_number toc_depth_1\">3<\/span> Installing Docker on Your VPS (Step by Step)<\/a><ul><li><a href=\"#1_Install_prerequisites\"><span class=\"toc_number toc_depth_2\">3.1<\/span> 1. Install prerequisites<\/a><\/li><li><a href=\"#2_Add_Dockers_official_GPG_key_and_repository\"><span class=\"toc_number toc_depth_2\">3.2<\/span> 2. Add Docker\u2019s official GPG key and repository<\/a><\/li><li><a href=\"#3_Install_Docker_Engine_and_related_tools\"><span class=\"toc_number toc_depth_2\">3.3<\/span> 3. Install Docker Engine and related tools<\/a><\/li><li><a href=\"#4_Enable_and_test_Docker\"><span class=\"toc_number toc_depth_2\">3.4<\/span> 4. Enable and test Docker<\/a><\/li><li><a href=\"#5_Run_Docker_as_a_nonroot_user_optional_but_recommended\"><span class=\"toc_number toc_depth_2\">3.5<\/span> 5. Run Docker as a non\u2011root user (optional but recommended)<\/a><\/li><\/ul><\/li><li><a href=\"#Running_Your_First_Isolated_Container\"><span class=\"toc_number toc_depth_1\">4<\/span> Running Your First Isolated Container<\/a><ul><li><a href=\"#Basic_Nginx_container_on_a_VPS\"><span class=\"toc_number toc_depth_2\">4.1<\/span> Basic Nginx container on a VPS<\/a><\/li><li><a href=\"#Persisting_content_with_volumes\"><span class=\"toc_number toc_depth_2\">4.2<\/span> Persisting content with volumes<\/a><\/li><li><a href=\"#Understanding_isolation_what_is_actually_separated\"><span class=\"toc_number toc_depth_2\">4.3<\/span> Understanding isolation: what is actually separated?<\/a><\/li><\/ul><\/li><li><a href=\"#Structuring_Multiple_Isolated_Containers_on_One_VPS\"><span class=\"toc_number toc_depth_1\">5<\/span> Structuring Multiple Isolated Containers on One VPS<\/a><ul><li><a href=\"#Create_a_separate_Docker_network_per_project\"><span class=\"toc_number toc_depth_2\">5.1<\/span> Create a separate Docker network per project<\/a><\/li><li><a href=\"#One_container_per_responsibility\"><span class=\"toc_number toc_depth_2\">5.2<\/span> One container per responsibility<\/a><\/li><li><a href=\"#Using_docker_compose_for_bigger_stacks\"><span class=\"toc_number toc_depth_2\">5.3<\/span> Using docker compose for bigger stacks<\/a><\/li><li><a href=\"#Restart_policies_to_keep_containers_alive\"><span class=\"toc_number toc_depth_2\">5.4<\/span> Restart policies to keep containers alive<\/a><\/li><\/ul><\/li><li><a href=\"#Security_and_Isolation_Best_Practices_for_Docker_on_a_VPS\"><span class=\"toc_number toc_depth_1\">6<\/span> Security and Isolation Best Practices for Docker on a VPS<\/a><ul><li><a href=\"#1_Limit_the_Docker_API_and_daemon_exposure\"><span class=\"toc_number toc_depth_2\">6.1<\/span> 1. Limit the Docker API and daemon exposure<\/a><\/li><li><a href=\"#2_Prefer_nonroot_containers\"><span class=\"toc_number toc_depth_2\">6.2<\/span> 2. Prefer non\u2011root containers<\/a><\/li><li><a href=\"#3_Run_containers_with_the_minimum_privileges_they_need\"><span class=\"toc_number toc_depth_2\">6.3<\/span> 3. Run containers with the minimum privileges they need<\/a><\/li><li><a href=\"#4_Keep_images_small_and_up_to_date\"><span class=\"toc_number toc_depth_2\">6.4<\/span> 4. Keep images small and up to date<\/a><\/li><li><a href=\"#5_Use_the_VPS_firewall_as_a_second_boundary\"><span class=\"toc_number toc_depth_2\">6.5<\/span> 5. Use the VPS firewall as a second boundary<\/a><\/li><\/ul><\/li><li><a href=\"#Monitoring_Logs_and_Backups_for_Docker_on_a_VPS\"><span class=\"toc_number toc_depth_1\">7<\/span> Monitoring, Logs and Backups for Docker on a VPS<\/a><ul><li><a href=\"#Basic_monitoring_and_log_inspection\"><span class=\"toc_number toc_depth_2\">7.1<\/span> Basic monitoring and log inspection<\/a><\/li><li><a href=\"#Where_to_store_persistent_data\"><span class=\"toc_number toc_depth_2\">7.2<\/span> Where to store persistent data<\/a><\/li><li><a href=\"#Backing_up_Docker_data_on_a_VPS\"><span class=\"toc_number toc_depth_2\">7.3<\/span> Backing up Docker data on a VPS<\/a><\/li><li><a href=\"#Automating_lifecycle_tasks\"><span class=\"toc_number toc_depth_2\">7.4<\/span> Automating lifecycle tasks<\/a><\/li><\/ul><\/li><li><a href=\"#Putting_It_All_Together_A_Simple_MultiContainer_Example\"><span class=\"toc_number toc_depth_1\">8<\/span> Putting It All Together: A Simple Multi\u2011Container Example<\/a><ul><li><a href=\"#Scenario_a_small_web_app_with_a_database\"><span class=\"toc_number toc_depth_2\">8.1<\/span> Scenario: a small web app with a database<\/a><\/li><li><a href=\"#Highlevel_design\"><span class=\"toc_number toc_depth_2\">8.2<\/span> High\u2011level design<\/a><\/li><li><a href=\"#Example_using_docker_compose_YAML_sketch\"><span class=\"toc_number toc_depth_2\">8.3<\/span> Example using docker compose (YAML sketch)<\/a><\/li><\/ul><\/li><li><a href=\"#Conclusion_Your_Next_Step_with_Isolated_Docker_on_a_VPS\"><span class=\"toc_number toc_depth_1\">9<\/span> Conclusion: Your Next Step with Isolated Docker on a VPS<\/a><\/li><\/ul><\/div>\n<h2><span id=\"Why_Run_Docker_on_a_VPS_Instead_of_Installing_Everything_Directly\">Why Run Docker on a VPS Instead of Installing Everything Directly?<\/span><\/h2>\n<p>Before jumping into commands, it is worth understanding what you actually gain by putting Docker on a VPS instead of installing applications directly on the host.<\/p>\n<h3><span id=\"Containers_vs_virtual_machines_in_simple_terms\">Containers vs virtual machines in simple terms<\/span><\/h3>\n<p>A VPS is already a virtual machine: it has its own virtual CPU, RAM, storage and network, isolated from other customers on the same physical server. Docker works one layer above this. Instead of virtualizing hardware, containers share the host kernel but isolate processes, file systems, users and network namespaces.<\/p>\n<p>In practice, this means:<\/p>\n<ul>\n<li><strong>Lighter than VMs:<\/strong> containers start in milliseconds and use less RAM and disk space.<\/li>\n<li><strong>Reproducible:<\/strong> you define everything in a Dockerfile and can recreate the same environment on another VPS easily.<\/li>\n<li><strong>Isolated:<\/strong> projects do not see each other\u2019s processes, ports or dependencies unless you explicitly connect them.<\/li>\n<li><strong>Disposable:<\/strong> you can destroy and recreate containers without losing data stored in volumes.<\/li>\n<\/ul>\n<p>We covered how this trend is reshaping hosting in our article about <a href='https:\/\/www.dchost.com\/blog\/en\/vps-teknolojilerinde-konteynerlesme-trendi\/'>containerization trends in VPS technology<\/a>. Here we focus on the hands\u2011on part.<\/p>\n<h3><span id=\"Realworld_problems_Docker_solves_on_a_VPS\">Real\u2011world problems Docker solves on a VPS<\/span><\/h3>\n<ul>\n<li><strong>Conflicting dependencies:<\/strong> one app needs PHP 7.4, another PHP 8.2. With Docker, each runs in its own image.<\/li>\n<li><strong>Port conflicts:<\/strong> you want two different Nginx or Node.js apps, both listening on port 80. Docker lets you map host ports independently.<\/li>\n<li><strong>Messy upgrades:<\/strong> instead of upgrading libraries on the host and risking breakage, you update the image and roll back if needed.<\/li>\n<li><strong>Simple migration:<\/strong> move your containers and volumes from one dchost.com VPS to another with very predictable behavior.<\/li>\n<\/ul>\n<p>For small and medium projects, this gives you a nice middle ground between traditional shared hosting and full Kubernetes clusters: you keep control, but without unnecessary complexity.<\/p>\n<h2><span id=\"Preparing_Your_VPS_for_Docker\">Preparing Your VPS for Docker<\/span><\/h2>\n<p>You can run Docker on most modern Linux distributions. On dchost.com we usually recommend Ubuntu, Debian or one of the RHEL\u2011compatible distros like AlmaLinux or Rocky Linux. If you are still deciding, our detailed comparison of distributions in <a href='https:\/\/www.dchost.com\/blog\/en\/vps-icin-linux-dagitimi-secimi-ubuntu-debian-almalinux-ve-rocky-linux-karsilastirmasi\/'>choosing a Linux distro for your VPS<\/a> can help.<\/p>\n<h3><span id=\"Minimum_VPS_requirements\">Minimum VPS requirements<\/span><\/h3>\n<p>Docker itself is lightweight, but your containers still need CPU, RAM and storage. As a baseline:<\/p>\n<ul>\n<li><strong>1 vCPU \/ 1 GB RAM:<\/strong> enough for testing, small personal projects and a few low\u2011traffic containers.<\/li>\n<li><strong>2\u20134 vCPU \/ 4\u20138 GB RAM:<\/strong> better for real applications (e.g., one or two websites, a database and background workers).<\/li>\n<li><strong>Fast SSD \/ NVMe:<\/strong> containers do many small I\/O operations; NVMe VPS plans at dchost.com will noticeably improve responsiveness.<\/li>\n<\/ul>\n<p>Disk space depends on the base images (each image can be hundreds of MB) and your data. Plan at least 20\u201340 GB for small stacks; more if you store media, logs or databases.<\/p>\n<h3><span id=\"Update_and_secure_the_base_system\">Update and secure the base system<\/span><\/h3>\n<p>Before installing Docker, make sure your VPS is updated and not exposed with default settings. We go much deeper in our security guides like <a href='https:\/\/www.dchost.com\/blog\/en\/vps-sunucu-guvenligi-nasil-saglanir-kapiyi-acik-birakmadan-yasamanin-sirri\/'>how to secure a VPS server without leaving doors open<\/a> and the more checklist\u2011oriented <a href='https:\/\/www.dchost.com\/blog\/en\/vps-guvenlik-sertlestirme-kontrol-listesi-sshd_config-fail2ban-ve-root-erisimini-kapatmak\/'>VPS security hardening checklist<\/a>, but here is a minimum:<\/p>\n<ol>\n<li><strong>Connect via SSH<\/strong> with the credentials provided by dchost.com.<\/li>\n<li><strong>Update packages<\/strong> (Ubuntu\/Debian example):<br \/>\n    <code>sudo apt update &amp;&amp; sudo apt upgrade -y<\/code><\/li>\n<li><strong>Create a non\u2011root user<\/strong> and give it sudo access if your image did not already create one.<\/li>\n<li><strong>Harden SSH:<\/strong> disable password login if you can, use key\u2011based auth, change root Login settings.<\/li>\n<li><strong>Enable a firewall:<\/strong> allow only SSH (port 22) and HTTP\/HTTPS (80\/443) at first.<br \/>\n    For example with ufw on Ubuntu:<br \/>\n    <code>sudo ufw allow OpenSSH<br \/>sudo ufw allow 80\/tcp<br \/>sudo ufw allow 443\/tcp<br \/>sudo ufw enable<\/code><\/li>\n<\/ol>\n<p>If you want to go further with SSH protection, the article <a href='https:\/\/www.dchost.com\/blog\/en\/vpste-ssh-guvenligi-nasil-saglamlasir-fido2-anahtarlari-ssh-ca-ve-rotasyonun-sicacik-yolculugu\/'>VPS SSH hardening without the drama<\/a> shows modern options like FIDO2 keys and SSH certificate authorities.<\/p>\n<h2><span id=\"Installing_Docker_on_Your_VPS_Step_by_Step\">Installing Docker on Your VPS (Step by Step)<\/span><\/h2>\n<p>We will use Ubuntu LTS as the main example, but the logic is similar on other distributions. Always prefer distribution packages provided by Docker rather than random scripts from the internet.<\/p>\n<h3><span id=\"1_Install_prerequisites\">1. Install prerequisites<\/span><\/h3>\n<p>On Ubuntu\/Debian:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo apt update\nsudo apt install -y ca-certificates curl gnupg lsb-release<\/code><\/pre>\n<h3><span id=\"2_Add_Dockers_official_GPG_key_and_repository\">2. Add Docker\u2019s official GPG key and repository<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo mkdir -p \/etc\/apt\/keyrings\ncurl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg | \n  sudo gpg --dearmor -o \/etc\/apt\/keyrings\/docker.gpg\n\necho \n  &quot;deb [arch=$(dpkg --print-architecture) signed-by=\/etc\/apt\/keyrings\/docker.gpg] \n  https:\/\/download.docker.com\/linux\/ubuntu \n  $(lsb_release -cs) stable&quot; | \n  sudo tee \/etc\/apt\/sources.list.d\/docker.list &gt; \/dev\/null\n\nsudo apt update<\/code><\/pre>\n<h3><span id=\"3_Install_Docker_Engine_and_related_tools\">3. Install Docker Engine and related tools<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin<\/code><\/pre>\n<p>This installs:<\/p>\n<ul>\n<li><strong>docker-ce:<\/strong> main Docker engine (daemon and CLI).<\/li>\n<li><strong>containerd:<\/strong> the runtime that actually runs containers.<\/li>\n<li><strong>docker buildx \/ compose:<\/strong> extended build features and the modern <code>docker compose<\/code> subcommand.<\/li>\n<\/ul>\n<h3><span id=\"4_Enable_and_test_Docker\">4. Enable and test Docker<\/span><\/h3>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo systemctl enable docker\nsudo systemctl start docker\nsudo docker run hello-world<\/code><\/pre>\n<p>If everything is correct, Docker will pull the <code>hello-world<\/code> image and print a confirmation message.<\/p>\n<h3><span id=\"5_Run_Docker_as_a_nonroot_user_optional_but_recommended\">5. Run Docker as a non\u2011root user (optional but recommended)<\/span><\/h3>\n<p>By default you need <code>sudo<\/code> to run Docker commands. For development and low\u2011risk scenarios you can add your non\u2011root user to the <code>docker<\/code> group:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo usermod -aG docker $USER\n# Log out and back in, then test:\ndocker ps<\/code><\/pre>\n<p>For stricter security, consider rootless Docker or user namespace remapping. We will touch on those in the hardening section and you can dive deeper in our dedicated article <a href='https:\/\/www.dchost.com\/blog\/en\/bir-konteyner-gununde-kafama-takilanlar\/'>how we ship safer containers with rootless Docker and Podman<\/a>.<\/p>\n<h2><span id=\"Running_Your_First_Isolated_Container\">Running Your First Isolated Container<\/span><\/h2>\n<p>Let us move beyond <code>hello-world<\/code> and run a simple web server in a container. We will use Nginx as an example, but the same model applies to any application image.<\/p>\n<h3><span id=\"Basic_Nginx_container_on_a_VPS\">Basic Nginx container on a VPS<\/span><\/h3>\n<p>Start with a single command:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">docker run -d \n  --name demo-nginx \n  -p 80:80 \n  nginx:alpine<\/code><\/pre>\n<p>What this does:<\/p>\n<ul>\n<li><code>-d<\/code> runs the container in the background (detached mode).<\/li>\n<li><code>--name demo-nginx<\/code> gives the container a predictable name.<\/li>\n<li><code>-p 80:80<\/code> maps port 80 on your VPS to port 80 inside the container.<\/li>\n<li><code>nginx:alpine<\/code> is a lightweight Nginx image based on Alpine Linux.<\/li>\n<\/ul>\n<p>Open your VPS IP in a browser. You should see the Nginx welcome page, served from inside the container.<\/p>\n<h3><span id=\"Persisting_content_with_volumes\">Persisting content with volumes<\/span><\/h3>\n<p>By default, changes inside containers disappear when you recreate them. For web content, logs and databases, you should use volumes (bind mounts or Docker volumes):<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">mkdir -p ~\/docker\/demo-nginx\/html\ncat &gt; ~\/docker\/demo-nginx\/html\/index.html &lt;&lt; 'EOF'\n&lt;h1&gt;Hello from Docker on my VPS&lt;\/h1&gt;\nEOF\n\ndocker stop demo-nginx &amp;&amp; docker rm demo-nginx\n\ndocker run -d \n  --name demo-nginx \n  -p 80:80 \n  -v ~\/docker\/demo-nginx\/html:\/usr\/share\/nginx\/html:ro \n  nginx:alpine<\/code><\/pre>\n<p>Now Nginx serves your custom <code>index.html<\/code>. The important part:<\/p>\n<ul>\n<li><code>-v host:container:ro<\/code> mounts your host directory into the container read\u2011only, keeping your content outside the container image.<\/li>\n<li>You can destroy and recreate the container without losing data in <code>~\/docker\/demo-nginx\/html<\/code>.<\/li>\n<\/ul>\n<h3><span id=\"Understanding_isolation_what_is_actually_separated\">Understanding isolation: what is actually separated?<\/span><\/h3>\n<p>When we say \u201cisolated Docker containers\u201d, we are mainly talking about:<\/p>\n<ul>\n<li><strong>Process isolation:<\/strong> processes inside a container see only their own PID namespace, not host processes.<\/li>\n<li><strong>File system isolation:<\/strong> each container has its own file system view based on the image plus mounted volumes.<\/li>\n<li><strong>Network isolation:<\/strong> containers live on a virtual network; only exposed ports are reachable from the VPS network interface.<\/li>\n<li><strong>Resource limits:<\/strong> using cgroups, you can limit CPU and memory per container.<\/li>\n<\/ul>\n<p>This is why you can safely run multiple versions of the same service (e.g., Nginx, MySQL) on one VPS without conflicts, as long as they use different ports or networks.<\/p>\n<h2><span id=\"Structuring_Multiple_Isolated_Containers_on_One_VPS\">Structuring Multiple Isolated Containers on One VPS<\/span><\/h2>\n<p>Once you are comfortable with a single container, the next step is running multiple applications side by side in a way that stays maintainable over time. Here are key building blocks.<\/p>\n<h3><span id=\"Create_a_separate_Docker_network_per_project\">Create a separate Docker network per project<\/span><\/h3>\n<p>Docker networks let containers talk to each other by name while being isolated from other networks. For each project or client, create a dedicated network:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">docker network create project1-net<\/code><\/pre>\n<p>Then attach containers to this network:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">docker run -d \n  --name project1-web \n  --network project1-net \n  -p 8080:80 \n  nginx:alpine<\/code><\/pre>\n<p>If you add a database container on the same network, the web container can reach it via its container name (e.g., <code>project1-db<\/code>) without you exposing the DB port to the whole internet.<\/p>\n<h3><span id=\"One_container_per_responsibility\">One container per responsibility<\/span><\/h3>\n<p>A common beginner mistake is trying to put everything in a single container: web server, application runtime, database, cron jobs, etc. This makes updates and scaling harder. A better pattern is:<\/p>\n<ul>\n<li><strong>web:<\/strong> Nginx or Caddy for static files and reverse proxy.<\/li>\n<li><strong>app:<\/strong> PHP\u2011FPM, Node.js, Python, etc., serving the application.<\/li>\n<li><strong>db:<\/strong> MySQL, MariaDB, PostgreSQL, Redis, etc.<\/li>\n<li><strong>worker:<\/strong> background job runners, queues, schedulers.<\/li>\n<\/ul>\n<p>You then connect these containers on the same project network and expose only the web container\u2019s HTTP\/HTTPS ports to the outside world.<\/p>\n<h3><span id=\"Using_docker_compose_for_bigger_stacks\">Using docker compose for bigger stacks<\/span><\/h3>\n<p>For more than a couple of containers, typing long <code>docker run<\/code> commands quickly becomes painful. The typical next step is declaring your stack in a <code>docker-compose.yml<\/code> file and running everything with a single command:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">docker compose up -d<\/code><\/pre>\n<p>Compose is ideal for common setups like WordPress, Laravel or custom microservices. If you are interested in a production\u2011ready example, see our practical guide <a href='https:\/\/www.dchost.com\/blog\/en\/docker-compose-ile-wordpress-nginx-mariadb-redis-nasil-tatli-tatli-akiyor-kalici-hacimler-otomatik-yedek-ve-guncelleme-akisi\/'>WordPress on Docker Compose with Nginx, MariaDB, Redis and automatic backups<\/a>. Even if you are not running WordPress, the patterns apply to most PHP and Node.js apps.<\/p>\n<h3><span id=\"Restart_policies_to_keep_containers_alive\">Restart policies to keep containers alive<\/span><\/h3>\n<p>On a VPS you want your containers to start automatically after a reboot or a crash. Add a restart policy to your <code>docker run<\/code> or compose definitions:<\/p>\n<ul>\n<li><code>--restart unless-stopped<\/code> \u2013 restart on failure and on boot, unless you manually stop the container.<\/li>\n<li><code>--restart on-failure<\/code> \u2013 restart only when the container exits with an error.<\/li>\n<\/ul>\n<p>Example:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">docker run -d \n  --name demo-nginx \n  --restart unless-stopped \n  -p 80:80 \n  nginx:alpine<\/code><\/pre>\n<h2><span id=\"Security_and_Isolation_Best_Practices_for_Docker_on_a_VPS\">Security and Isolation Best Practices for Docker on a VPS<\/span><\/h2>\n<p>Docker\u2019s defaults are reasonable for development, but a VPS that is reachable from the internet requires a bit more care. The good news: with a few habits you dramatically reduce risk while keeping things simple.<\/p>\n<h3><span id=\"1_Limit_the_Docker_API_and_daemon_exposure\">1. Limit the Docker API and daemon exposure<\/span><\/h3>\n<ul>\n<li><strong>Do not expose Docker\u2019s TCP API<\/strong> to the public internet unless you really know what you are doing.<\/li>\n<li>By default Docker listens on a Unix socket (<code>\/var\/run\/docker.sock<\/code>). Keep it that way and only allow trusted users to access it.<\/li>\n<\/ul>\n<h3><span id=\"2_Prefer_nonroot_containers\">2. Prefer non\u2011root containers<\/span><\/h3>\n<p>Many official images still run processes as root inside the container by default. While a container boundary exists, escaping from a root process can have more serious consequences than from an unprivileged user.<\/p>\n<p>Practical steps:<\/p>\n<ul>\n<li>Choose images that document non\u2011root usage (e.g., specifying <code>USER<\/code> in Dockerfile or environment variables).<\/li>\n<li>Override the user when starting the container if the image supports it: <code>docker run -u 1000:1000 ...<\/code><\/li>\n<li>Investigate rootless Docker or user namespace remapping for stricter isolation, as discussed in <a href='https:\/\/www.dchost.com\/blog\/en\/bir-konteyner-gununde-kafama-takilanlar\/'>our container security deep dive<\/a>.<\/li>\n<\/ul>\n<h3><span id=\"3_Run_containers_with_the_minimum_privileges_they_need\">3. Run containers with the minimum privileges they need<\/span><\/h3>\n<p>Avoid <code>--privileged<\/code> unless you have a very specific reason. Some safer flags and patterns:<\/p>\n<ul>\n<li><code>--read-only<\/code> to make the container\u2019s file system read\u2011only, mounting only necessary paths as writable volumes.<\/li>\n<li><code>--cap-drop ALL --cap-add ...<\/code> to remove all Linux capabilities and add back only the ones an app truly needs.<\/li>\n<li>Use <code>--tmpfs<\/code> for temporary directories that should not persist on disk.<\/li>\n<\/ul>\n<h3><span id=\"4_Keep_images_small_and_up_to_date\">4. Keep images small and up to date<\/span><\/h3>\n<p>Every extra package in your image is an extra potential vulnerability. Prefer minimal images (e.g., <code>alpine<\/code> or distro\u2011provided slim variants) and update them regularly:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># Pull newer versions\ndocker pull nginx:alpine\n\n# Recreate containers with new images\ndocker compose pull\ndocker compose up -d<\/code><\/pre>\n<p>Combine this with a regular VPS patching routine as described in our guide <a href='https:\/\/www.dchost.com\/blog\/en\/vps-sunucu-guvenligi-nasil-saglanir-kapiyi-acik-birakmadan-yasamanin-sirri\/'>how to secure a VPS server<\/a>.<\/p>\n<h3><span id=\"5_Use_the_VPS_firewall_as_a_second_boundary\">5. Use the VPS firewall as a second boundary<\/span><\/h3>\n<p>Even though Docker exposes only the ports you map, it is a good idea to keep the VPS firewall as your source of truth:<\/p>\n<ul>\n<li>Open 80\/443 only if the container actually serves HTTP\/HTTPS.<\/li>\n<li>Keep database ports (3306, 5432, 6379, etc.) closed externally and reachable only through Docker networks.<\/li>\n<li>Consider rate limiting and connection limits on exposed ports for extra safety.<\/li>\n<\/ul>\n<p>For more advanced packet\u2011level rules with IPv4 and IPv6, our cookbook on <a href='https:\/\/www.dchost.com\/blog\/en\/nftables-ile-vps-guvenlik-duvari-rehberi-rate-limit-port-knocking-ve-ipv6-kurallari-nasil-tatli-tatli-kurulur\/'>using nftables as a firewall on a VPS<\/a> can help you build a layered defence.<\/p>\n<h2><span id=\"Monitoring_Logs_and_Backups_for_Docker_on_a_VPS\">Monitoring, Logs and Backups for Docker on a VPS<\/span><\/h2>\n<p>Once your containers are running, you must be able to see what is happening and protect your data. Containers are ephemeral; your data should not be.<\/p>\n<h3><span id=\"Basic_monitoring_and_log_inspection\">Basic monitoring and log inspection<\/span><\/h3>\n<ul>\n<li><code>docker ps<\/code> \u2013 list running containers.<\/li>\n<li><code>docker ps -a<\/code> \u2013 list all containers (including stopped).<\/li>\n<li><code>docker logs &lt;name&gt;<\/code> \u2013 view container logs.<\/li>\n<li><code>docker stats<\/code> \u2013 view CPU and RAM usage per container.<\/li>\n<\/ul>\n<p>At the VPS level, tools like <code>htop<\/code>, <code>iotop<\/code>, Netdata, Prometheus and Grafana are great additions. We have a dedicated article that walks through this stack step by step in <a href='https:\/\/www.dchost.com\/blog\/en\/vps-kaynak-kullanimi-izleme-rehberi-htop-iotop-netdata-ve-prometheus\/'>monitoring VPS resource usage with htop, iotop, Netdata and Prometheus<\/a>, which combines nicely with containerized workloads.<\/p>\n<h3><span id=\"Where_to_store_persistent_data\">Where to store persistent data<\/span><\/h3>\n<p>There are two main patterns:<\/p>\n<ul>\n<li><strong>Bind mounts:<\/strong> host paths like <code>\/srv\/project1\/db<\/code> are mounted into containers. Easy to understand and back up but ties you to the host layout.<\/li>\n<li><strong>Named volumes:<\/strong> managed by Docker and stored under Docker\u2019s data directory. Clean abstraction but you need to use <code>docker volume<\/code> commands to interact.<\/li>\n<\/ul>\n<p>For most small projects we recommend bind mounts under a clear directory structure, for example:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">\/srv\/project1\/\n  app\/\n  db\/\n  logs\/\n\/srv\/project2\/\n  app\/\n  db\/\n  logs\/<\/code><\/pre>\n<p>Then mount them into containers, e.g. <code>-v \/srv\/project1\/db:\/var\/lib\/mysql<\/code>.<\/p>\n<h3><span id=\"Backing_up_Docker_data_on_a_VPS\">Backing up Docker data on a VPS<\/span><\/h3>\n<p>Backups should focus on data and configuration, not container internals. Typically you back up:<\/p>\n<ul>\n<li>Application code (if not already version\u2011controlled in Git).<\/li>\n<li>Database data directories or dumps (MySQL\/MariaDB, PostgreSQL, etc.).<\/li>\n<li>Uploaded files and user content (images, documents, etc.).<\/li>\n<li>Configuration files such as <code>docker-compose.yml<\/code>, env files and Nginx configs.<\/li>\n<\/ul>\n<p>Common strategies:<\/p>\n<ul>\n<li>Use <code>docker exec<\/code> to run database dumps on a schedule and store them in a host directory.<\/li>\n<li>Use rsync or rclone from the host to sync <code>\/srv<\/code> to object storage.<\/li>\n<li>Use backup tools like restic or Borg directly on the VPS.<\/li>\n<\/ul>\n<p>We show practical, encrypted and versioned setups with these tools in <a href='https:\/\/www.dchost.com\/blog\/en\/restic-ve-borg-ile-s3-uyumlu-uzak-yedekleme-surumleme-sifreleme-ve-saklama-ne-zaman-nasil\/'>our guide to offsite backups with Restic\/Borg to S3\u2011compatible storage<\/a>. The same principles apply whether your workloads run in Docker or on bare metal.<\/p>\n<h3><span id=\"Automating_lifecycle_tasks\">Automating lifecycle tasks<\/span><\/h3>\n<p>As you grow more comfortable, you can automate:<\/p>\n<ul>\n<li><strong>Image updates:<\/strong> scheduled pulls and rolling restarts.<\/li>\n<li><strong>Backup jobs:<\/strong> cron jobs or systemd timers calling scripts that dump databases and push to remote storage.<\/li>\n<li><strong>Health checks:<\/strong> scripts or external services that hit containerized endpoints and alert you if things go down.<\/li>\n<\/ul>\n<p>If you already use tools like Terraform and Ansible to automate VPS setup, you can reuse those patterns for Docker stacks as well. Our article on <a href='https:\/\/www.dchost.com\/blog\/en\/terraform-ve-ansible-ile-vps-otomasyonu-ayni-sunucuyu-tek-tusla-kurmak\/'>automating VPS setup with Terraform and Ansible<\/a> shows how to consistently bring new servers to the exact same state, including Docker installation and configuration.<\/p>\n<h2><span id=\"Putting_It_All_Together_A_Simple_MultiContainer_Example\">Putting It All Together: A Simple Multi\u2011Container Example<\/span><\/h2>\n<p>To consolidate everything, let us sketch a small, realistic setup you might run on a dchost.com VPS using only core Docker features (no orchestration system required).<\/p>\n<h3><span id=\"Scenario_a_small_web_app_with_a_database\">Scenario: a small web app with a database<\/span><\/h3>\n<p>Requirements:<\/p>\n<ul>\n<li>Public website at <code>https:\/\/example.com<\/code>.<\/li>\n<li>Application written in PHP or Node.js.<\/li>\n<li>MySQL or PostgreSQL database.<\/li>\n<li>Everything isolated from another project on the same VPS.<\/li>\n<\/ul>\n<h3><span id=\"Highlevel_design\">High\u2011level design<\/span><\/h3>\n<ul>\n<li>Create a dedicated Docker network: <code>project1-net<\/code>.<\/li>\n<li>Run three containers: <code>project1-web<\/code>, <code>project1-app<\/code>, <code>project1-db<\/code>.<\/li>\n<li>Expose only <code>project1-web<\/code> (port 80\/443) to the internet.<\/li>\n<li>Mount host directories under <code>\/srv\/project1<\/code> for persistent data and configuration.<\/li>\n<\/ul>\n<h3><span id=\"Example_using_docker_compose_YAML_sketch\">Example using docker compose (YAML sketch)<\/span><\/h3>\n<p>This is not a full production file, but it shows how isolation, networking and volumes come together:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">version: '3.9'\nservices:\n  db:\n    image: mariadb:10.11\n    container_name: project1-db\n    restart: unless-stopped\n    environment:\n      - MYSQL_ROOT_PASSWORD=supersecret\n      - MYSQL_DATABASE=project1\n      - MYSQL_USER=project1\n      - MYSQL_PASSWORD=project1pass\n    volumes:\n      - \/srv\/project1\/db:\/var\/lib\/mysql\n    networks:\n      - project1-net\n\n  app:\n    image: myregistry\/project1-app:latest\n    container_name: project1-app\n    restart: unless-stopped\n    environment:\n      - DB_HOST=project1-db\n      - DB_NAME=project1\n      - DB_USER=project1\n      - DB_PASS=project1pass\n    depends_on:\n      - db\n    networks:\n      - project1-net\n\n  web:\n    image: nginx:alpine\n    container_name: project1-web\n    restart: unless-stopped\n    ports:\n      - '80:80'\n      - '443:443'\n    volumes:\n      - \/srv\/project1\/nginx\/conf.d:\/etc\/nginx\/conf.d:ro\n      - \/srv\/project1\/app\/public:\/var\/www\/html:ro\n      - \/etc\/letsencrypt:\/etc\/letsencrypt:ro\n    depends_on:\n      - app\n    networks:\n      - project1-net\n\nnetworks:\n  project1-net:\n    driver: bridge<\/code><\/pre>\n<p>With this pattern, adding a second project on the same VPS is simply a matter of creating a new network, a new directory under <code>\/srv\/project2<\/code> and another compose file. The projects stay nicely isolated even though they share the same VPS resources.<\/p>\n<h2><span id=\"Conclusion_Your_Next_Step_with_Isolated_Docker_on_a_VPS\">Conclusion: Your Next Step with Isolated Docker on a VPS<\/span><\/h2>\n<p>Running isolated Docker containers on a VPS gives you a powerful balance of control, simplicity and safety. Instead of fighting with conflicting packages and ad\u2011hoc scripts, you describe each service in a Dockerfile or compose file, keep data in well\u2011defined volumes and let Docker handle process lifecycles and networking. On a well\u2011sized VPS from dchost.com, this pattern scales gracefully from a single personal site to dozens of small client projects, all without needing a full Kubernetes stack.<\/p>\n<p>If you are just starting, focus on a single application: install Docker, run a basic container, mount a volume and experiment with networks. Then gradually introduce compose files, non\u2011root containers, resource limits and automated backups. Combine the practices in this guide with the security and monitoring patterns we share across our blog, and you will have a clean, repeatable blueprint for almost any new project. When you are ready to host more apps or need extra CPU, RAM or NVMe storage, you can simply upgrade your dchost.com VPS plan or add another VPS and reuse the exact same Docker workflows.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>If you are deploying multiple apps on a single VPS, Docker is one of the simplest ways to keep everything neat, isolated and easy to manage. Instead of manually installing PHP, Node.js, databases and tools side by side on the same operating system, you bundle each application into its own container and run them in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3645,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-3643","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/3643","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=3643"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/3643\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/3645"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=3643"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=3643"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=3643"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}