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. Key features Git-based deployments : Push to deploy from GitHub with zero-downtime rollouts and instant rollback. : 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. : Python, Node.js, PHP... basically anything that can run on Docker. Environment management : Multiple environments with branch mapping and encrypted environment variables. : Multiple environments with branch mapping and encrypted environment variables. Real-time monitoring : Live and searchable build and runtime logs. : Live and searchable build and runtime logs. Team collaboration : Role-based access control with team invitations and permissions. : Role-based access control with team invitations and permissions. Custom domains : Support for custom domain and automatic Let's Encrypt SSL certificates. : 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 User documentation: devpu.sh/docs Technical documentation: ARCHITECTURE 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: Sign in or sign up for a Hetzner account: Hetzner Cloud Console Generate an API token: Creating an API token 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 | cpx 51> ] 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 . 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. SSH into your new server: The provision script will have created a user for you. ssh < login_user > @ < server_ip > 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 SSH into the server: ssh < login_user > @ < server_ip > Install /dev/push: curl -fsSL https://raw.githubusercontent.com/hunvreus/devpush/main/scripts/prod/install.sh | sudo bash Switch to devpush user: sudo -iu devpush 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 Visit your URL: https:// 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 Install Colima and the Loki Docker plugin: scripts/dev/install.sh Set up environment variables: cp .env.dev.example .env Start the stack (streams logs): scripts/dev/start.sh Add --prune to prune dangling images before build to prune dangling images before build Add --cache to use the build cache (default is no cache) 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 ' '` 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: Identifying and authorizing users : Callback URL : add two callback URLs with your domain: https://example.com/api/github/authorize/callback https://example.com/auth/github/callback Expire user authorization tokens : No : Post installation : Setup URL : https://example.com/api/github/install/callback Redirect on update : Yes : Webhook : Active : Yes Webhook URL : https://example.com/api/github/webhook : Permissions : Repository permissions Administration : Read and write Checks : Read and write Commit statuses : Read and write Contents : Read and write Deployments : Read and write Issues : Read and write Metadata : Read-only Pull requests : Read and write Webhook : Read and write Account permissions : Email addresses : Read-only : Subscribe to events : Installation target Push Repository : 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. : edit . If missing, running will sed an allow‑all file. Production: edit /srv/devpush/access.json on the server. Rules format (any/all may be used): { "emails" : [ " [email protected] " ], "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" : " [email protected] " , "provider" : " google " , "ip" : " 203.0.113.10 " , "user_agent" : " Mozilla/5.0 " } License MIT