Skip to content

hunvreus/devpush

Repository files navigation

/dev/push

An open-source and self-hostable alternative to Vercel, Render, Netlify and the likes. It allows you to build and deploy any app (Python, Node.js, PHP, ...) with zero-downtime updates, real-time logs, team management, customizable environments and domains, etc.

A screenshot of a deployment in /dev/push.

Key features

  • Git-based deployments: Push to deploy from GitHub with zero-downtime rollouts and instant rollback.
  • Multi-language support: Python, Node.js, PHP... basically anything that can run on Docker.
  • Environment management: Multiple environments with branch mapping and encrypted environment variables.
  • Real-time monitoring: Live and searchable build and runtime logs.
  • Team collaboration: Role-based access control with team invitations and permissions.
  • Custom domains: Support for custom domain and automatic Let's Encrypt SSL certificates.
  • Self-hosted and open source: Run on your own servers, MIT licensed.

Support the project

Documentation

Quickstart

⚠️ Supported on Ubuntu/Debian. Other distros may work but aren't officially supported (yet).

Log in your server, run the following command and follow instructions:

curl -fsSL https://raw.githubusercontent.com/hunvreus/devpush/main/scripts/prod/install.sh | sudo bash

You user must have sudo privileges.

Install & Update

Prerequisites

You will need a fresh Ubuntu/Debian server you can SSH into with sudo privileges. We recommend a CPX31 from Hetzner.

You can use the provisioning script to get a server up and running:

  1. Sign in or sign up for a Hetzner account: Hetzner Cloud Console
  2. Generate an API token: Creating an API token
  3. Provision a server (requires --token; optional: --user, --name, --region, --type):
    curl -fsSL https://raw.githubusercontent.com/hunvreus/devpush/main/scripts/prod/provision-hetzner.sh | bash -s -- --token <hetzner_api_key> [--user <login_user>] [--name <hostname>] [--region <fsn1|nbg1|hel1|ash|hil|sin>] [--type <cpx11|cpx21|cpx31|cpx41|cpx51>]
    Tip: run curl -fsSL https://raw.githubusercontent.com/hunvreus/devpush/main/scripts/prod/provision-hetzner.sh | bash -s -- --help to list regions and types (with specs). Defaults: region hil, type cpx31.
  4. Configure DNS Records: Go to your DNS provider and create two A records pointing at the server IP for APP_HOSTNAME (e.g. app.devpu.sh) and a wildcard on subdomains of DEPLOY_DOMAIN (e.g. *.devpush.app). If you're using Cloudflare, set SSL/TLS to "Full (strict)" and keep the records proxied.
  5. SSH into your new server: The provision script will have created a user for you.
    ssh <login_user>@<server_ip>
  6. Run hardening for system and SSH:
curl -fsSL https://raw.githubusercontent.com/hunvreus/devpush/main/scripts/prod/harden.sh | sudo bash -s -- --ssh

Even if you already have a server, we recommend you harden security (ufw, fail2ban, disabled root SSH, etc). You can do that using scripts/prod/harden.sh.

Install

  1. SSH into the server:
    ssh <login_user>@<server_ip>
  2. Install /dev/push:
    curl -fsSL https://raw.githubusercontent.com/hunvreus/devpush/main/scripts/prod/install.sh | sudo bash
  3. Switch to devpush user:
sudo -iu devpush
  1. Edit .env:
cd devpush && vi .env

Tip: you will need to fill in at least the following: LE_EMAIL, APP_HOSTNAME, DEPLOY_DOMAIN, EMAIL_SENDER_ADDRESS, RESEND_API_KEY and your GitHub app settings (see [environment-variables] for details). SERVER_IP, SECRET_KEY, ENCRYPTION_KEY, POSTGRES_PASSWORD should be pre-filled. You can ignore all commented out environment variables. 5. Start services:

scripts/prod/start.sh --migrate
  1. Visit your URL: https://<APP_HOSTNAME>

Update

The follwing commands must be run as devpush user (su - devpush).

In most cases, you can run an update with:

scripts/prod/update.sh --all

Alternatively, you can force a full upgrade (with downtime) using:

scripts/prod/update.sh --full -y

You can update specific components:

scripts/prod/update.sh --components <component_name>

Development

⚠️ Development scripts target macOS for now.

Install

  1. Install Colima and the Loki Docker plugin:
    scripts/dev/install.sh
  2. Set up environment variables:
    cp .env.dev.example .env
  3. Start the stack (streams logs):
    scripts/dev/start.sh
    • Add --prune to prune dangling images before build
    • Add --cache to use the build cache (default is no cache)
  4. Initialize your database once containers are up:
    scripts/dev/db-migrate.sh

See the scripts section for more dev utilities.

Update

  • The app is mounted inside containers, so code changes reflect immediately. Some SSE endpoints may require closing browser tabs to trigger a reload.
  • The workers require a restart:
    docker-compose restart worker-arq
  • To apply migrations:
    scripts/dev/db-migrate.sh

Scripts

Area Script What it does
Dev scripts/dev/install.sh Setup Colima and install Loki Docker plugin
Dev scripts/dev/start.sh Start stack with logs (foreground); supports --prune, --cache
Dev scripts/dev/build-runners.sh Build runner images (default no cache; --cache to enable)
Dev scripts/dev/db-generate.sh Generate Alembic migration (prompts for message)
Dev scripts/dev/db-migrate.sh Apply Alembic migrations
Dev scripts/dev/db-reset.sh Drop and recreate public schema in DB
Dev scripts/dev/clean.sh Stop stack and clean dev data (--hard for global)
Prod scripts/prod/provision-hetzner.sh Provision a Hetzner server (API token, regions from API, fixed sizes)
Prod scripts/prod/install.sh Server setup: Docker, Loki plugin, user, clone repo, create .env
Prod scripts/prod/harden.sh System hardening (UFW, fail2ban, unattended-upgrades); add --ssh to harden SSH
Prod scripts/prod/start.sh Start services; optional --migrate
Prod scripts/prod/stop.sh Stop services (--down for hard stop)
Prod scripts/prod/restart.sh Restart services; optional --migrate
Prod scripts/prod/update.sh Update by tag; --all (app+workers), --full (downtime), or --components
Prod scripts/prod/db-migrate.sh Apply DB migrations in production
Prod scripts/prod/check-env.sh Validate required keys exist in .env
Prod scripts/prod/update/app.sh Blue‑green update for app
Prod scripts/prod/update/worker-arq.sh Drain‑aware blue‑green update for worker-arq
Prod scripts/prod/update/worker-monitor.sh Blue‑green update for worker-monitor

Environment variables

Variable Comments Default
APP_NAME App name. /dev/push
APP_DESCRIPTION App description. Deploy your Python app without touching a server.
URL_SCHEME http (development) or https (production). https
LE_EMAIL Email used to register the Let's Encrypt (ACME) account in Traefik; receives certificate issuance/renewal/expiry notifications. ""
APP_HOSTNAME Domain for the app (e.g. app.devpu.sh). ""
DEPLOY_DOMAIN Domain used for deployments (e.g. devpush.app if you want your deployments available at *.devpush.app). APP_HOSTNAME
SERVER_IP Public IP of the server ""
SECRET_KEY App secret for sessions/CSRF. Generate: openssl rand -hex 32 ""
ENCRYPTION_KEY Fernet key (urlsafe base64, 32 bytes). Generate: `openssl rand -base64 32 tr '+/' '-_'
EMAIL_LOGO URL for email logo image. Only helpful for testing, as the app will use app/logo-email.png if left empty. ""
EMAIL_SENDER_NAME Name displayed as email sender for invites/login. ""
EMAIL_SENDER_ADDRESS Email sender used for invites/login. ""
RESEND_API_KEY API key for Resend. ""
GITHUB_APP_ID GitHub App ID. ""
GITHUB_APP_NAME GitHub App name. ""
GITHUB_APP_PRIVATE_KEY GitHub App private key (PEM format). ""
GITHUB_APP_WEBHOOK_SECRET GitHub webhook secret for verifying webhook payloads. ""
GITHUB_APP_CLIENT_ID GitHub OAuth app client ID. ""
GITHUB_APP_CLIENT_SECRET GitHub OAuth app client secret. ""
GOOGLE_CLIENT_ID Google OAuth client ID. ""
GOOGLE_CLIENT_SECRET Google OAuth client secret. ""
POSTGRES_DB PostgreSQL database name. devpush
POSTGRES_USER PostgreSQL username. devpush-app
POSTGRES_PASSWORD PostgreSQL password. Generate: `openssl rand -base64 24 tr -d '\n'`
REDIS_URL Redis connection URL. redis://redis:6379
DOCKER_HOST Docker daemon host address. tcp://docker-proxy:2375
UPLOAD_DIR Directory for file uploads. /app/upload
TRAEFIK_CONFIG_DIR Traefik configuration directory. /data/traefik
DEFAULT_CPU_QUOTA Default CPU quota for containers (microseconds). 100000
DEFAULT_MEMORY_MB Default memory limit for containers (MB). 4096
JOB_TIMEOUT Job timeout in seconds. 320
JOB_COMPLETION_WAIT Job completion wait time in seconds. 300
DEPLOYMENT_TIMEOUT Deployment timeout in seconds. 300
LOG_LEVEL Logging level. WARNING
DB_ECHO Enable SQL query logging. false
ENV Environment (development/production). development
ACCESS_DENIED_MESSAGE Message shown to users who are denied access based on sign-in access control. Sign-in not allowed for this email.
ACCESS_DENIED_WEBHOOK Optional webhook to receive denied events (read more about Sign-in access control). ""
LOGIN_HEADER HTML snippet displayed above the login form. ""
TOASTER_HEADER HTML snippet displayed at the top of the toaster (useful to display a permanent toast on all pages). ""

GitHub App

You will need to configure a GitHub App with the following settings:

Sign-in access control

Provide an access rules file to restrict who can sign up/sign in.

  • Development: edit ./access.json. If missing, running scripts/dev/start.sh will sed an allow‑all file.
  • Production: edit /srv/devpush/access.json on the server.

Rules format (any/all may be used):

{
  "emails": ["alice@example.com"],
  "domains": ["example.com"],
  "globs": ["*@corp.local", "*.dept.example.com"],
  "regex": ["^[^@]+@(eng|research)\\.example\\.com$"]
}

Globs use shell-style wildcards; regex are Python patterns. If the file is missing or empty, all valid emails are allowed.

Additionally, if you set the ACCESS_DENIED_WEBHOOK environment variable, denied sign-in attempts will be posted to the provided URL with the following payload:

{
  "email": "user@example.com",
  "provider": "google",
  "ip": "203.0.113.10",
  "user_agent": "Mozilla/5.0"
}

License

MIT