{"id":733,"date":"2026-07-27T23:33:53","date_gmt":"2026-07-27T23:33:53","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=733"},"modified":"2026-07-27T23:33:53","modified_gmt":"2026-07-27T23:33:53","slug":"vps-cicd-pipeline-github-actions-docker-setup-guide","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/","title":{"rendered":"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A CI\/CD pipeline automates the process of testing, building, and deploying your application every time you push code. Running the pipeline on your own VPS gives you full control over the environment, no per-minute build charges, and the ability to cache Docker layers across deployments. Before choosing a VPS plan for your CI\/CD runner, <a href=\"https:\/\/virtualserversvps.com\/#providers\">compare VPS performance across providers<\/a> to ensure you get enough CPU and memory for fast builds. This guide shows you how to set up a GitHub Actions self-hosted runner on your VPS, configure automatic Docker builds, and deploy your application with zero downtime.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture Overview<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is what the pipeline looks like end to end:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Developer pushes code to a GitHub repository.<\/li><li>GitHub Actions triggers a workflow on a self-hosted runner installed on your VPS.<\/li><li>The runner checks out the code, runs tests, builds a Docker image, and pushes it to a container registry (GitHub Container Registry or Docker Hub).<\/li><li>The runner SSHes into the production VPS (or the same VPS) and deploys the new container with zero-downtime via a Docker Compose rollout.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Prepare Your VPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start with a fresh VPS running Ubuntu 24.04 LTS. SSH in and update the system:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update &amp;&amp; sudo apt upgrade -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install Docker and Docker Compose:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install docker.io docker-compose-v2 -y\nsudo systemctl enable --now docker\nsudo usermod -aG docker $USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Log out and back in for the group change to take effect. Verify Docker works:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run --rm hello-world<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Register a GitHub Actions Self-Hosted Runner<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Go to your GitHub repository, click Settings, then Actions, then Runners, then &#8220;New self-hosted runner.&#8221; Select Linux as the operating system and x64 as the architecture. GitHub will show you a download URL and a token \u2014 copy them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On your VPS, create a dedicated user for the runner and download the software:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo useradd -m -s \/bin\/bash github-runner\nsudo su - github-runner\ncurl -o actions-runner-linux-x64-2.321.0.tar.gz -L https:\/\/github.com\/actions\/runner\/releases\/download\/v2.321.0\/actions-runner-linux-x64-2.321.0.tar.gz\ntar xzf actions-runner-linux-x64-2.321.0.tar.gz\n.\/config.sh --url https:\/\/github.com\/your-username\/your-repo --token YOUR_TOKEN<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When prompted, enter the runner group (default is fine) and the runner name (e.g., &#8220;vps-runner&#8221;). Choose the &#8220;run as service&#8221; option for persistence. Start the runner:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo .\/svc.sh install github-runner\nsudo .\/svc.sh start<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the runner is connected by checking the GitHub Actions settings page \u2014 it should show a green &#8220;Idle&#8221; status next to your runner.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Create the GitHub Actions Workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In your repository, create a file at <code>.github\/workflows\/deploy.yml<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Deploy to VPS\n\non:\n  push:\n    branches: [ main ]\n\njobs:\n  build-and-deploy:\n    runs-on: self-hosted\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Build Docker image\n        run: docker build -t my-app:${{ github.sha }} .\n\n      - name: Deploy via Docker Compose\n        run: |\n          export IMAGE_TAG=${{ github.sha }}\n          docker compose -f docker-compose.prod.yml up -d --no-deps web\n          docker image prune -f<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Configure Zero-Downtime Deployment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a <code>docker-compose.prod.yml<\/code> file in your repository that uses the image tag passed from the workflow:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>version: '3.8'\nservices:\n  web:\n    image: my-app:${IMAGE_TAG:-latest}\n    ports:\n      - \"80:80\"\n    environment:\n      - NODE_ENV=production\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http:\/\/localhost\/health\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n      start_period: 15s<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>--no-deps web<\/code> flag in the deploy step tells Docker Compose to only recreate the web service without touching dependencies (like databases). The health check ensures the new container is accepting traffic before the old one is removed, giving you zero-downtime deployments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Set Up Docker Layer Caching for Faster Builds<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Builds on a self-hosted runner can be much faster than GitHub-hosted runners because you can cache Docker layers between runs. Add these lines to your workflow to enable caching:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>- name: Build with cache\n  run: |\n    docker build       --cache-from=my-app:latest       -t my-app:${{ github.sha }}       -t my-app:latest .<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This tells Docker to use the <code>latest<\/code> tag as a cache source. If your <code>Dockerfile<\/code> is well-structured (with dependencies installed before source code COPY commands), Docker will reuse cached layers for package installs and only rebuild layers that changed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Secure the Runner<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A self-hosted runner has full access to your VPS. Take these security measures:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Run the runner under a non-root user<\/strong> with limited permissions. The <code>github-runner<\/code> user we created earlier is a good start.<\/li><li><strong>Restrict what repositories can use the runner<\/strong> in GitHub Runner settings.<\/li><li><strong>Use Docker containers for builds<\/strong> instead of running commands directly on the host. This limits what the workflow can access.<\/li><li><strong>Enable Docker content trust<\/strong> and sign your images if deploying to production.<\/li><li><strong>Add a firewall rule<\/strong> to only allow SSH access from your IP and HTTP\/HTTPS from the public internet.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting Common CI\/CD Issues on VPS<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Runner goes offline<\/strong>: Check the runner service status with <code>sudo .\/svc.sh status<\/code> and check logs at <code>~\/actions-runner\/_diag<\/code>.<\/li><li><strong>Builds run out of memory<\/strong>: A 1 GB RAM VPS may struggle with Docker builds. Allocate at least 2 GB for comfortable builds, or add swap space.<\/li><li><strong>Docker socket permission denied<\/strong>: Ensure the <code>github-runner<\/code> user is in the <code>docker<\/code> group: <code>sudo usermod -aG docker github-runner<\/code>.<\/li><li><strong>Deployment port conflicts<\/strong>: If you deploy multiple applications, use unique host ports or a reverse proxy like Nginx to route traffic.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Running your own CI\/CD pipeline on a VPS gives you unlimited build minutes, full control over the environment, and Docker layer caching that makes deployments faster than most hosted CI services. Start small with a single repository and one self-hosted runner, then scale to multiple runners or Kubernetes when your needs grow. <a href=\"https:\/\/virtualserversvps.com\/#providers\">Check our VPS provider benchmarks<\/a> to find a plan with the CPU performance and RAM your CI\/CD workloads demand.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A CI\/CD pipeline automates the process of testing, building, and deploying your application every time you push code. Running the pipeline on your own VPS gives you full control over&#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-733","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>How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker - 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-cicd-pipeline-github-actions-docker-setup-guide\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker\" \/>\n<meta property=\"og:description\" content=\"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-27T23:33:53+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-cicd-pipeline-github-actions-docker-setup-guide\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/\",\"name\":\"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-07-27T23:33:53+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker\"}]},{\"@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":"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker - 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-cicd-pipeline-github-actions-docker-setup-guide\/","og_locale":"en_US","og_type":"article","og_title":"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker","og_description":"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker","og_url":"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-07-27T23:33:53+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-cicd-pipeline-github-actions-docker-setup-guide\/","url":"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/","name":"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-07-27T23:33:53+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/vps-cicd-pipeline-github-actions-docker-setup-guide\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to Set Up a CI\/CD Pipeline on Your VPS with GitHub Actions and Docker"}]},{"@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\/733","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=733"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/733\/revisions"}],"predecessor-version":[{"id":735,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/733\/revisions\/735"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}