{"id":973,"date":"2026-08-25T22:36:08","date_gmt":"2026-08-25T22:36:08","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=973"},"modified":"2026-08-25T22:36:08","modified_gmt":"2026-08-25T22:36:08","slug":"vps-staging-environment-docker-compose","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/","title":{"rendered":"Setting Up a Staging Environment on a Single VPS with Docker Compose"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Every production deployment carries risk. A staging environment \u2014 a full copy of your stack that you can test against before pushing to production \u2014 reduces that risk dramatically. But dedicated staging servers are expensive, and most single-VPS setups cannot justify the cost. The solution is to run staging alongside production on the same VPS using Docker Compose, with careful isolation through separate Compose projects, distinct ports, and reverse-proxy routing. This guide walks through the entire setup. If you are still choosing a host, our <a href=\"https:\/\/virtualserversvps.com\/#providers\">VPS comparison table<\/a> helps you find one with enough RAM to run both environments comfortably.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Docker Compose Instead of Separate VPSes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A second VPS for staging doubles your costs \u2014 not just the monthly bill, but also the maintenance overhead of patching, monitoring, and securing two servers. Docker Compose lets you define your entire stack (web server, application server, database, cache, queue) in a single <code>docker-compose.yml<\/code> file and run multiple isolated instances of that stack on the same host. Each instance gets its own network namespace, volumes, and environment variables. The production instance listens on ports 80\/443; the staging instance listens on a high port like 8080, or you route it through a subdomain via a reverse proxy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>A VPS with at least 2 GB RAM (4 GB recommended for most stacks). Check our <a href=\"https:\/\/virtualserversvps.com\/#features\">memory and specs comparison<\/a> to find a suitable plan.<\/li><li>Docker and Docker Compose installed. If you are on Ubuntu 22.04+, install with: <code>sudo apt install docker.io docker-compose-v2<\/code>.<\/li><li>A domain or subdomain for staging (e.g., <code>staging.yourdomain.com<\/code>) or a separate port number.<\/li><li>Git access to your project repository.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Structure Your Project for Two Environments<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The key to running multiple environments on one host is Docker Compose&#8217;s project name feature. When you run <code>docker compose -p staging up -d<\/code>, Docker prefixes all container names, networks, and volumes with <code>staging_<\/code>, keeping them completely separate from the <code>production_<\/code> prefix. Your project directory should look like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/opt\/\n\u251c\u2500\u2500 production\/\n\u2502   \u251c\u2500\u2500 docker-compose.yml\n\u2502   \u251c\u2500\u2500 .env\n\u2502   \u2514\u2500\u2500 nginx\/\n\u2514\u2500\u2500 staging\/\n    \u251c\u2500\u2500 docker-compose.yml\n    \u251c\u2500\u2500 .env\n    \u2514\u2500\u2500 nginx\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each <code>docker-compose.yml<\/code> defines the same services but with different port mappings, environment variables, and volume mounts. The <code>.env<\/code> file in each directory sets the differences: database names, API keys, and domain names.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Docker Compose Configuration for Staging<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is a minimal <code>docker-compose.yml<\/code> for a typical LEMP stack staging environment:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>version: \"3.9\"\nservices:\n  db:\n    image: mysql:8.0\n    container_name: staging_db\n    volumes:\n      - staging_db_data:\/var\/lib\/mysql\n    environment:\n      MYSQL_ROOT_PASSWORD: ${STAGING_DB_ROOT_PASS}\n      MYSQL_DATABASE: ${STAGING_DB_NAME}\n    ports:\n      - \"127.0.0.1:3307:3306\"\n    restart: unless-stopped\n\n  app:\n    image: your-app:staging\n    container_name: staging_app\n    depends_on:\n      - db\n    environment:\n      DB_HOST: db\n      DB_NAME: ${STAGING_DB_NAME}\n      DB_USER: ${STAGING_DB_USER}\n      DB_PASS: ${STAGING_DB_PASS}\n      APP_ENV: staging\n    volumes:\n      - .\/app:\/var\/www\/html\n    restart: unless-stopped\n\n  web:\n    image: nginx:alpine\n    container_name: staging_web\n    depends_on:\n      - app\n    volumes:\n      - .\/nginx\/staging.conf:\/etc\/nginx\/conf.d\/default.conf\n      - .\/app:\/var\/www\/html\n    ports:\n      - \"8080:80\"\n    restart: unless-stopped\n\nvolumes:\n  staging_db_data:<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Key differences from production: the database port is 3307 (not 3306), the web port is 8080 (not 80), and the container names are prefixed with <code>staging_<\/code>. The MySQL port is bound to 127.0.0.1 only, so it is not accessible from outside the VPS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Use a Reverse Proxy for Domain-Based Routing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of remembering port numbers, route staging traffic through a subdomain. Install Nginx on the host (not in Docker) and add a virtual host configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n    listen 80;\n    server_name staging.yourdomain.com;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:8080;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then run certbot to get a Let&#8217;s Encrypt certificate for the staging subdomain: <code>sudo certbot --nginx -d staging.yourdomain.com<\/code>. This gives you HTTPS on staging just like production, which is important for testing features that depend on secure contexts (service workers, payment flows, OAuth redirects).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Data Isolation and Seed Data<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Staging should use a separate database with either anonymized production data or synthetic test data. Never point staging at your production database \u2014 a staging bug could corrupt real data. Script the database seeding process:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# scripts\/seed-staging.sh\ndocker compose -p staging exec -T db mysql -u root -p\"$STAGING_DB_ROOT_PASS\"   $STAGING_DB_NAME &lt; .\/backups\/anonymized_prod.sql<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Run this script after each staging deployment to refresh the data. For application file storage (user uploads, media), either use a separate volume or configure your application to use a staging-specific storage backend.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Deployment Workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With this setup, your deployment workflow becomes:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Push code to a staging branch in your git repository.<\/li><li>SSH into the VPS and pull the staging branch.<\/li><li>Rebuild the staging image: <code>docker compose -p staging build<\/code>.<\/li><li>Restart: <code>docker compose -p staging up -d<\/code>.<\/li><li>Run migration and seed scripts against the staging database.<\/li><li>Test at <code>https:\/\/staging.yourdomain.com<\/code>.<\/li><li>If everything passes, merge to main and deploy to production: <code>docker compose -p production up -d<\/code>.<\/li><\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This workflow catches environment-specific bugs (wrong config values, missing dependencies, database migration conflicts) that unit tests running in CI cannot detect. The cost is zero extra VPS spend \u2014 just the disk space for the staging volumes and the RAM for the extra containers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Resource Limits and Monitoring<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On a single VPS, staging containers must not starve production. Set resource limits in each Compose file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>services:\n  app:\n    deploy:\n      resources:\n        limits:\n          cpus: \"0.5\"\n          memory: \"512M\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Monitor resource usage with <code>docker stats<\/code> and set up alerts for when the staging environment approaches your VPS&#8217;s memory ceiling. If you are consistently above 80% RAM usage with both environments running, consider upgrading to a plan with more memory \u2014 our <a href=\"https:\/\/virtualserversvps.com\/#features\">memory and specs comparison<\/a> can help you identify the right tier.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A staging environment on a single VPS is not a compromise \u2014 it is a pragmatic solution for teams and solo developers who need production-like testing without a second server bill. Docker Compose&#8217;s project isolation makes it safe, reverse-proxy routing makes it accessible, and resource limits keep it from interfering with production. The next time you deploy without testing on staging first, remember: the cost of a staging environment is near zero; the cost of a production outage is not. <a href=\"https:\/\/virtualserversvps.com\/#providers\">Compare VPS plans<\/a> to find one with enough headroom for both environments.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Every production deployment carries risk. A staging environment \u2014 a full copy of your stack that you can test against before pushing to production \u2014 reduces that risk dramatically. But&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-973","post","type-post","status-publish","format-standard","hentry","category-vps-guides-tutorials"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Setting Up a Staging Environment on a Single VPS with Docker Compose - Virtual Servers VPS Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Setting Up a Staging Environment on a Single VPS with Docker Compose\" \/>\n<meta property=\"og:description\" content=\"Setting Up a Staging Environment on a Single VPS with Docker Compose\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-25T22:36:08+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/\",\"name\":\"Setting Up a Staging Environment on a Single VPS with Docker Compose - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-08-25T22:36:08+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Setting Up a Staging Environment on a Single VPS with Docker Compose\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Setting Up a Staging Environment on a Single VPS with Docker Compose - Virtual Servers VPS Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/","og_locale":"en_US","og_type":"article","og_title":"Setting Up a Staging Environment on a Single VPS with Docker Compose","og_description":"Setting Up a Staging Environment on a Single VPS with Docker Compose","og_url":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-08-25T22:36:08+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/","url":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/","name":"Setting Up a Staging Environment on a Single VPS with Docker Compose - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-08-25T22:36:08+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/vps-staging-environment-docker-compose\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Setting Up a Staging Environment on a Single VPS with Docker Compose"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/973","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=973"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/973\/revisions"}],"predecessor-version":[{"id":974,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/973\/revisions\/974"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=973"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=973"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=973"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}