Git Push to Deploy on Your Own Server: Five Ways, From a Git Hook to a Platform

Nick Potts, who builds Ployz. Published , updated .

You can get git push deploy on your own server in five realistic ways, and the cheapest is a 10-line git hook on a $7 server. The difference between them isn't price, since all five run on the same rented box. It's what happens around the push: whether the build can fail without taking the site down, whether you can roll back, and whether every pull request gets its own preview.

Facts checked 2026-10-01 against Git's, GitHub's, Dokku's, Kamal's, Coolify's and Dokploy's own docs, linked next to each claim. I haven't benchmarked these tools for this article. The examples are short versions of what the official docs show.

I moved off platforms because the bills kept climbing. My first month on Laravel Cloud was about $600, and Railway was $500+ a month and climbing, mostly from a busy database. Today one dedicated server at €80 a month runs everything. The thing I missed most after leaving was git push and walking away, so this is the article I wanted back then.

What does each git push deploy option handle?

All five options deploy when you push. They differ on zero-downtime deploys, rollbacks and per-PR previews. A bare git hook does nothing except what you script. GitHub Actions adds logs and secrets. Dokku and Kamal add health-checked, zero-downtime deploys and a rollback path. Coolify, Dokploy and Ployz add a UI and preview environments for pull requests.

Git hookGitHub Actions + SSHDokkuKamalCoolify / Dokploy / Ployz
Triggergit push to your serverPush to GitHubgit push to your serverYou run kamal deploy (or CI does)Push to your Git host
Builds the appYour scriptYour workflowBuildpacks, Dockerfile or imageDocker build, pushed to a registryYes
Zero downtimeOnly if you script itOnly if you script itYes, by defaultYes, via kamal-proxyYes (see each below)
RollbacksScript itRe-run an old workflowForce-push an old commitkamal rollbackCoolify, Dokploy: yes. Ployz: no
PR previewsNoScript itVia the GitHub ActionNoYes
SecretsFiles on the serverGitHub secretsdokku config.kamal/secretsDashboard
Git hostsAnyGitHubAnyAnyCoolify, Dokploy: several. Ployz: GitHub only

The server under all five is the same. A Netcup VPS 500 is €8.26 a month incl. VAT (2 vCPU, 4 GB RAM), and a Hetzner CX23 is $6.49 plus $0.60 for an IPv4 address in Europe. If you want the wider picture of what platforms charge for the same app, start at the PaaS pricing hub.

How do I set up git push deploy with a git hook?

Create a bare repository on the server, add a post-receive hook that checks out the pushed branch into your app directory and restarts the app, then add the server as a git remote. Git runs the hook after every successful push. It's free, works with any Git host and takes ten minutes. Everything beyond "copy files and restart" is yours to write.

On the server:

git init --bare /srv/git/app.git
mkdir -p /srv/app

Then save this as /srv/git/app.git/hooks/post-receive and chmod +x it:

#!/bin/sh
# Git passes one line per updated ref: <old-oid> <new-oid> <ref-name>
while read oldrev newrev ref; do
  if [ "$ref" = "refs/heads/main" ]; then
    git --work-tree=/srv/app --git-dir=/srv/git/app.git checkout -f main
    cd /srv/app && docker compose up -d --build
  fi
done

On your laptop:

git remote add production deploy@your-server:/srv/git/app.git
git push production main

The line format and timing come from Git's githooks docs: the hook runs after all refs are updated and "cannot affect the outcome" of the push. The Pro Git book adds a warning worth taking seriously: the client "doesn't disconnect until it has completed", so a slow build holds your terminal open. If you want to refuse a bad push instead, that's pre-receive, which rejects the whole push when it exits non-zero.

What's left to you:

  • Downtime. docker compose up -d --build stops the old container and starts the new one. If the new one crashes on boot, the site is down until you fix it. There's no health check.
  • Rollbacks. None. You push an older commit or keep old release directories and switch a symlink yourself.
  • Secrets. A .env file on the server that you edit by hand and never commit.
  • Builds on the server. Building on a 4 GB box while it serves traffic works for small apps, and starts to hurt once builds need more memory than your app does.

I think this is the right answer for a single static site or a hobby app. For anything with paying users, you'll end up rebuilding half of Dokku in shell.

How do I deploy to a VPS with GitHub Actions?

Add a workflow that runs on push to main, loads an SSH key from a GitHub secret, copies your code with rsync and restarts the app over SSH. Logs and secrets live in GitHub, and private repos get 2,000 free minutes a month on GitHub Free. Downtime, rollbacks and previews are still your scripts.

A minimal .github/workflows/deploy.yml:

name: deploy
on:
  push:
    branches: [main]
concurrency: production
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v7
      - name: Set up SSH
        env:
          SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
          KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
        run: |
          mkdir -p ~/.ssh
          printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
      - name: Copy and restart
        run: |
          rsync -az --delete --exclude .git ./ [email protected]:/srv/app/
          ssh [email protected] 'cd /srv/app && docker compose up -d --build'

How to handle the secrets safely:

  • Pass secrets through env:, not on the command line. GitHub's secrets docs warn that command-line arguments can be visible via ps or captured in audit events, and recommend environment variables with proper quoting. That's why the key goes through $SSH_KEY above.
  • Pin the host key. Store the output of ssh-keyscan your-server in DEPLOY_KNOWN_HOSTS once, after checking it, instead of turning off host key checking.
  • Use a deploy user with limited rights, not root. If the key leaks, the damage is that user's.
  • Forks don't get your secrets. Apart from GITHUB_TOKEN, "secrets are not passed to the runner when a workflow is triggered from a forked repository" (same page). Good for safety, and the reason PR previews from forks need extra thought.
  • Log redaction covers GitHub secrets only. Anything else sensitive needs ::add-mask::.

The concurrency line stops two pushes from deploying over each other, and environment: production lets you add environment-scoped secrets and a required reviewer in the repo settings.

You can also build a Docker image in Actions, push it to a registry and have the server pull it. That moves the build off your server, which helps a small box. On cost, GitHub's Actions billing page says standard hosted runners are free in public repos, GitHub Free includes 2,000 minutes a month for private repos, and self-hosted runners are free.

What's left to you: the same list as the git hook. The restart is still a stop-and-start, a failed boot still means downtime, and rollback means re-running an older workflow run and hoping nothing else changed. It's GitHub only; GitLab has its own CI with the same shape.

How does Dokku's git push deploy work?

Dokku gives you Heroku's workflow on your own server: create an app, add a git remote, push. It builds with Heroku buildpacks by default (or your Dockerfile), runs the app in Docker, and waits for the new container before stopping the old one. It's free and open source. What it doesn't do on its own is roll back with a button or show a free web UI.

From Dokku's deployment docs:

# on the server
dokku apps:create ruby-getting-started

# on your laptop
git remote add dokku [email protected]:ruby-getting-started
git push dokku main

What it handles:

  • Builds. Heroku buildpacks via Herokuish by default, plus Dockerfiles and Docker images (same page). Your app has to listen on $PORT.
  • Zero downtime. On by default. Per the zero-downtime docs, Dokku waits 10 seconds after starting each new container before treating it as up, then waits another 60 seconds before stopping the old ones. You can define proper health checks in app.json. Turning checks off "will stop old containers before new ones start".
  • Previews. Not built into Dokku itself, but the official Dokku GitHub Action has a review-apps:create command that makes a review app with dokku apps:clone and a matching review-apps:destroy.
  • Secrets. Environment variables per app, set with dokku config:set.

What's left to you: rollbacks are a force-push of an older commit (git push --force dokku <sha>:main), which rebuilds it. That's a community recipe, not a documented button. Out of the box Dokku runs everything on the one server it's installed on (more servers means its k3s scheduler, which is Kubernetes), and the free version is CLI only. If Dokku feels like the right shape, it's the closest thing on this list to Heroku, and the comparisons in Railway alternatives cover how it stacks up against hosted options.

How does Kamal deploy, and is it git push?

Kamal isn't git push by itself. You run kamal deploy, or a CI job runs it on push. It builds a Docker image, pushes it to a registry, pulls it on every server you list, and uses kamal-proxy to switch traffic only once the new container answers 200 OK on /up. It has a real kamal rollback command. There's no UI and no previews.

From the Kamal docs: gem install kamal, then kamal init to create config/deploy.yml, kamal setup for the first deploy and kamal deploy after that. A minimal config, from the configuration overview, with the secret part from the environment variables page:

service: myapp
image: my-user/myapp
servers:
  web:
    hosts:
      - 192.168.1.1
registry:
  username: my-user
  password:
    - KAMAL_REGISTRY_PASSWORD
env:
  secret:
    - DATABASE_PASSWORD
proxy:
  ssl: true

What it handles:

  • Zero downtime. kamal-proxy starts the new container, routes traffic to it once GET /up returns 200, then stops the old one.
  • Rollbacks. kamal rollback <version> starts a previous image that's still on the hosts, so "nothing needs to be downloaded from the registry". The catch, from the rollback docs: old containers are pruned after 3 days by default when you deploy.
  • Several servers. List more hosts and Kamal deploys to all of them.
  • Secrets. .kamal/secrets maps names to values, usually pulled from your environment or a password manager. Kamal's docs say that if you put secret values directly in that file, don't commit it.

What's left to you: a container registry and its credentials, a CI job if you want push-to-deploy, any per-PR preview setup, and databases (Kamal runs them as "accessories", but backups are yours). Kamal is the most solid option here that's still a plain CLI, and it came out of the Rails world, so Rails apps fit it best.

How do Coolify and Dokploy deploy on git push?

Both are free, open source platforms you install on your server, with a web UI. Connect a GitHub App and every push deploys; GitLab, Bitbucket and Gitea work through webhooks. Both build preview deployments for pull requests and both have rollbacks. You run the platform itself, on the same server as your apps.

Coolify. Its CI/CD docs list GitHub, GitLab, Bitbucket and Gitea. The GitHub App is the recommended route and sets up webhooks for you; public repos and deploy keys need manual webhooks. Preview deployments get their own URL per PR and are deleted when the PR is merged or closed. Rollbacks redeploy "an older application image retained on the server", and an image removed by cleanup can't be rolled back to. They don't reverse migrations.

Dokploy. Per its auto deploy docs, GitHub auto-deploys "without any configuration", and GitLab, Bitbucket and Gitea use a webhook URL. The branch in Dokploy has to match the branch you push, or you get a "Branch Not Match" error. You can also trigger a deploy through its API from CI. Preview deployments are off by default and GitHub only, with an option to require a PR label. Rollbacks come in two kinds: automatic on failed health checks under Docker Swarm, and manual ones that need a registry configured.

What's left to you with both: updating and backing up the platform itself, and backing up your databases.

How does Ployz handle git push deploys?

Connect GitHub, and every push builds on your own servers (or in your own GitHub Actions) and deploys. Every pull request gets a preview environment with its own HTTPS address on your servers. Stateless services deploy with zero downtime: with a health check set, the new version has to pass it before the old one stops. Ployz is GitHub only and has no rollbacks. Deploy from GitHub in the docs covers Wait for CI, watch paths and monorepos.

I build Ployz, so take this section as the builder's view, limits included:

  • Builds. No Dockerfile needed for Node, Python, Ruby, PHP, Go, Rust, Java and Elixir. Your own Dockerfile or any Docker image works too. Building in your own GitHub Actions uses your Actions minutes, which keeps big builds off a small server.
  • Zero downtime. Where a new replica fails its health check, the old one keeps serving. Services with a volume, such as a database, restart instead.
  • Previews. One per pull request. Settings you save on a preview ship with the merge.
  • Secrets. Encrypted secrets and variables in the dashboard, and dashboard edits are staged and reviewed before you deploy.
  • Migrations. Pre-deploy commands run before the new version goes live, with deploy history and build logs.

What it doesn't do, and what to do instead:

  • No rollbacks. To undo a bad deploy, git revert the commit and push, which builds and deploys again. If you need one-click rollbacks today, Kamal, Coolify or Dokploy have them.
  • GitHub only. No GitLab or Bitbucket. Use Coolify, Dokploy, Dokku or a git hook if your code lives elsewhere.
  • No database backups. One-click Postgres, Redis, MySQL and MongoDB are regular services with a volume, and backing them up is on you.

It's free on your own servers. If you're comparing it against the hosted platforms rather than other self-hosted tools, Vercel alternatives and Railway vs Vercel cover those bills.

Can git push deploy have zero downtime?

Yes, but only if something starts the new version, checks it's healthy, moves traffic and only then stops the old one. A plain git hook or SSH workflow running docker compose up doesn't do that; it stops and starts. Dokku, Kamal, Coolify, Dokploy and Ployz all do the health-check-then-switch step for stateless apps.

If you want it from a hook, you need two copies of the app and a reverse proxy you can repoint: start the new one on another port, poll its health endpoint, reload the proxy, then stop the old one. That's roughly what kamal-proxy does for you. Databases are different everywhere: a service with a single volume can't run two copies at once, so it restarts.

Which git push deploy option should I pick?

Pick the git hook if you have one static site or a hobby app and you want zero moving parts. Pick GitHub Actions over SSH if you already live in GitHub and want logs and secrets without a new tool. Pick Dokku if you want Heroku's exact workflow on one server. Pick Kamal if you want solid rollbacks and multi-server deploys from a CLI. Pick a platform if you want previews and a UI.

  • Pick a git hook if the app is small, downtime during a restart is fine, and you'd rather own every line.
  • Pick GitHub Actions + SSH if you want deploy logs, secrets and approvals in one place, and you'll script the restart yourself.
  • Pick Dokku if you liked Heroku, you're on one server, and the CLI is enough.
  • Pick Kamal if you want kamal rollback, several servers and no UI, and you're fine running a registry.
  • Pick Coolify or Dokploy if you need a UI, PR previews and rollbacks, or your code isn't on GitHub.
  • Pick your own server with Ployz if your code is on GitHub and you want push-to-deploy, a preview per PR and zero-downtime deploys across one or more servers, and you can live without rollbacks for now.

All five run on the same $7 server, which is the real point: the platform markup is optional. Ployz gives you push-to-deploy and preview environments on servers you rent, and it's free on your own servers.

Run the same app on a server you rent. Ployz is free on your own servers.

Deploy your app