Pushdeck Documentation

A complete guide to zero-downtime automated deployments.

How It Works

Pushdeck deploys your application by cloning a fresh copy of your repository, running your custom scripts, then switching your web server to the new release in one instant step. Your visitors never see partially-deployed code.

# What your server looks like after a few deployments:
/var/www/myapp/
├── releases/
│   ├── 20260401120000/     # old release (kept for rollback)
│   ├── 20260402090000/     # older release
│   └── 20260402160000/     # ← newest release
├── .env                    # shared, never inside a release
└── current -> releases/20260402160000/  # symlink your web server points to
Your Nginx/Apache config never changes. Point it at {deploy_path}/current once and forget it. Pushdeck updates current on every deployment. For a Laravel app, your document root would be {deploy_path}/current/public.

Deployment Pipeline

Every deployment runs the following steps in strict sequence. If any step fails, the deployment stops and your live release is left untouched.

1. Clone New Release all servers
2. Your hooks (dragged above Activate) selected servers
3. Activate New Release: switches traffic to the new release all servers
4. Your hooks (dragged below Activate) selected servers
5. Purge Old Releases: removes releases beyond your retain limit all servers

Hook ordering

Go to the Hooks tab and drag your hooks into the position you want. Hooks above Activate New Release run before the new release goes live; hooks below run after.

What You Need Before Starting

RequirementDetails
A Linux serverWith SSH access and git installed. You will use your existing SSH user (e.g. ubuntu), and that user needs write access to your deploy path.
SSH accessPushdeck generates an SSH key for you. You'll add its public key to ~/.ssh/authorized_keys on your server. You never need to share your own private keys.
GitHub or GitLabYour application code in a repository.
Personal Access TokenFrom GitHub or GitLab with read access to your repo. See section 3 for step-by-step instructions.

1. Create a Project

Log in and click New Project from the dashboard.

FieldWhat to enterExample
Name A label for this project, just for your reference. My Laravel App
Deploy Path The directory on your server where Pushdeck will store releases. Your SSH user must have write access to this path (see step 2). /var/www/myapp
Retain Releases How many old releases to keep for rollback. Recommended: 5. 5
Web server tip: Set your Nginx/Apache document root to /var/www/myapp/current (or /var/www/myapp/current/public for Laravel). Set it once and leave it. Pushdeck updates what current points to on every deploy.

2. Add Your Server

Pushdeck generates a unique SSH key pair for each server you add. You get a public key to place on your server. You never share any of your own private keys with Pushdeck. You stay in full control: remove the key from your server at any time and Pushdeck can no longer connect.

Step 1: Add the server in Pushdeck

On your project overview, click Add Server and fill in the details:

FieldWhat to enter
NameAny label you like, e.g. production or web-01.
HostYour server's IP address or domain, e.g. 203.0.113.10.
PortSSH port, usually 22.
UsernameThe user Pushdeck should connect as. Use your existing SSH user, e.g. ubuntu on AWS or root on a fresh VPS. That user needs read/write access to your deploy path.

Click Generate Key & Continue. Pushdeck creates the key pair and takes you to the server page where the public key is waiting for you.

Step 2: Add Pushdeck's public key to your server

Copy the public key shown on the server page. Then SSH into your server as the same user you entered in step 1 and run:

# Make sure the .ssh directory exists
mkdir -p ~/.ssh && chmod 700 ~/.ssh

# Open (or create) the authorized_keys file, paste the key on a new line, then save
nano ~/.ssh/authorized_keys

# Set the correct permissions
chmod 600 ~/.ssh/authorized_keys

# Make sure your user has write access to the deploy path
sudo chown -R ubuntu:ubuntu /var/www/your-app

Replace ubuntu and /var/www/your-app with your actual username and deploy path. If you are using a separate deployment user, SSH as that user instead and follow the same steps.

Step 3: Test the connection

Back in Pushdeck, click Test Connection on the project overview. You need a green connected badge before you can deploy.

The Deploy button stays disabled until every server shows connected. Run the connection test on each server first.
Revoking access is simple. Delete the Pushdeck public key line from authorized_keys on your server and the connection is gone immediately. Nothing to change in Pushdeck.
Using IP allowlists on your server? All deployments originate from Pushdeck's fixed IP address: 13.205.191.222. If your server restricts SSH access by IP, add this address to your allowlist so Pushdeck can connect.

3. Connect Your Repository

Click the Repository tab on your project, then Connect Repository.

Pushdeck uses your token only to clone the repository during deployments. We recommend using the most scoped token your provider supports so only that one repository is accessible, nothing else on your account.

GitHub

GitHub offers two types of tokens. We recommend the fine-grained token as it can be locked to a single repository with read-only access.

1

Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens. Or go directly to github.com/settings/personal-access-tokens/new.

2

Give it a name like Pushdeck - my-app and set an expiry.

3

Under Repository access, choose Only select repositories and pick the one repo you are deploying.

4

Under Permissions, expand Repository permissions, find Contents and set it to Read-only. That is the only permission needed.

5

Click Generate token, copy it immediately (GitHub only shows it once), and paste it in Pushdeck.

If you prefer classic tokens, create one with only the repo scope. Fine-grained is better because it restricts access to one repository instead of all of them.

GitLab

For GitLab, use a project access token rather than a personal access token. It is scoped to a single project and does not touch anything else on your account.

1

Open your GitLab project, go to Settings → Access Tokens.

2

Give it a name, set an expiry, choose role Reporter, and tick the read_repository scope only.

3

Click Create project access token, copy it immediately, and paste it in Pushdeck.

Project access tokens expire and can be rotated from the same Settings page without touching anything else in your GitLab account.

4. Deployment Hooks

Hooks are shell scripts that run on your servers during every deployment. Go to the Hooks tab and click Add Hook.

Hook fields

FieldDescription
NameA label shown in the pipeline, e.g. Install Dependencies.
ScriptShell commands to run, one per line. They execute from the release directory.
On ServersCheckboxes for which servers this hook runs on. Leave all unchecked to run on every server.
Run As (optional)Only needed if you want a specific command to run as a different OS user than the one Pushdeck connects as, e.g. restarting PHP-FPM as www-data. If your SSH user already owns your project files, you can leave this blank and everything just works.

After saving, drag the hook in the pipeline table to set when it runs. Drop it above Activate New Release to run before the new release goes live, or below to run after.

Laravel / PHP

Place these hooks above Activate so they run before the new release goes live:

# Install Composer dependencies (no dev packages)
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader

# Run database migrations
php artisan migrate --force

# Cache config, routes, and views for production
php artisan config:cache
php artisan route:cache
php artisan view:cache

Place these hooks below Activate so they run after the new release is live:

# Restart the queue worker
php artisan queue:restart

# Reload PHP-FPM (use your actual service name)
sudo systemctl reload php8.3-fpm
Shared .env file: Save your environment file under the Environment tab and Pushdeck writes it to {deploy_path}/.env on your server automatically. Every deployment then links it into the new release. You never have to manage it manually.
Need to preserve uploaded files or logs across deployments? Use Linked Folders (the button next to Add Hook on the Hooks page). Any folder you add there is kept in a shared directory and symlinked into every new release automatically, so data is never lost on deploy. See section 5 for details.

Node.js

# Install production dependencies
npm ci --omit=dev

# Build assets (if applicable)
npm run build
# After activate: reload with PM2
pm2 reload ecosystem.config.js --env production

Python / Django

Place these hooks above Activate so the release is ready before it goes live:

# Install dependencies
pip install -r requirements.txt

# Run database migrations (Django)
python manage.py migrate --noinput

# Collect static files (Django)
python manage.py collectstatic --noinput
# After activate: restart the app service (gunicorn, uvicorn, etc.)
sudo systemctl restart gunicorn

Generic

# Any shell commands work; they run from the release directory
bundle install --without development test
bundle exec rake assets:precompile

5. Linked Folders

Every deployment clones a fresh copy of your repository into a new release folder. That means anything generated at runtime (user uploads, logs, cache files) would be lost on the next deploy unless you tell Pushdeck to preserve it.

Linked Folders solve this. You define folder paths relative to your project root, and on every deploy Pushdeck:

  1. Creates the folder inside {deploy_path}/shared/ if it does not already exist.
  2. Removes that folder from the freshly cloned release.
  3. Creates a symlink from the release folder to the shared copy.

The result is that all releases point at the same physical folder on disk, so data accumulates across deployments instead of disappearing.

Where to configure

Go to the Hooks tab and click the Linked Folders button in the top right. Add one path per input field, then click Save.

Folder structure on the server

Given a deploy path of /var/www/myapp and a linked folder of storage, your server looks like this:

/var/www/myapp/
  shared/
    storage/              # real folder, never deleted
  releases/
    20260404120000/
      storage             # symlink to ../../shared/storage
    20260403110000/
      storage             # symlink to ../../shared/storage
  current                 # symlink to latest release
The shared/ directory is created automatically on your first deploy. You do not need to create it manually. On rollback, the previous release still points at the same shared folder, so no data is lost there either.

Laravel

Laravel generates files at runtime that must survive deployments. Add these two paths:

PathWhy
storageLaravel writes logs, compiled views, sessions, queue jobs, and uploaded files here. Without it, logs disappear and session-based users get logged out on every deploy.
public/uploadsIf you store user-uploaded files under public/ instead of storage/, preserve this path too.

Only needed if your app stores uploaded files on the local disk (not S3 or cloud storage). If so, add this as a hook below Activate:

php artisan storage:link

This creates a public/storage link so files saved to storage/app/public are accessible in the browser. It is safe to run on every deploy as it does nothing if the link already exists.

Other stacks

StackCommon paths to link
Node.jspublic/uploads, data, logs
Django / Pythonmedia (MEDIA_ROOT), staticfiles if collected at runtime
Ruby on Railspublic/uploads, log, tmp
WordPress / PHPwp-content/uploads
Any stackAny folder your app writes to at runtime that should not be wiped on deploy
Enter paths relative to your project root, without a leading slash. For example, use storage, not /var/www/myapp/storage. Paths starting with / or ../ are rejected.

6. Environment Variables

Click the Environment tab to store your .env file contents securely. On each deployment, Pushdeck automatically links this file into the release as .env so you never have to copy it manually. For a Laravel app, paste the full contents of your production .env here, including APP_KEY, database credentials, and any third-party API keys.

The environment file is encrypted at rest. You must click Unlock (and enter your password) before you can edit it. Never commit your .env file to your repository.

Your First Deploy

Before deploying, confirm:

  • All servers show a connected badge (click Test on each server).
  • A repository is connected.
  • Your environment file is saved under the Environment tab. Pushdeck writes it to your server automatically when you save. For a Laravel app, make sure APP_KEY is set before your first deploy.

Click Deploy Now from the project overview, the Deployments tab, or the dashboard. The deployment runs in the background and you will be taken to the deployment detail page where each pipeline step updates in real time.

Reading the Logs

The deployment detail page shows the pipeline as a table of steps. Each row is one step on one server.

ColumnMeaning
ServerWhich server this job ran on.
StatusFinished success   Failed error   Running in progress   Pending not started yet
Started / FinishedLocal timestamps (converted to your browser timezone).
DurationHow long that step took on that server.
Click to view the raw SSH output for that step in a modal.

When a step fails, all remaining steps are immediately marked as failed so you can see the full picture right away. Steps that ran successfully before the failure keep their output and timestamps. Click the eye icon to inspect them.

Rolling Back

Go to the Deployments tab. Any deployment with a success status that is not the current active release will show a Rollback button.

Clicking Rollback re-runs only the Activate step (and any hooks positioned below it) against the selected release without re-cloning. The old release folder is already on disk, so the switch is nearly instant.

Rollback is only possible for releases that are still on disk. If a release has been purged (beyond your retain limit), it cannot be restored.

CI/CD Webhook

Trigger a deployment automatically from GitHub Actions, GitLab CI, or any HTTP client by hitting your project's Deploy Hook URL. Find it on the project overview under Deploy Hook URL.

# Trigger a deploy with curl (POST)
curl -X POST https://yourpushdeck.com/deploy/YOUR_TOKEN

GitHub Actions example

- name: Deploy
  run: curl -X POST ${{ secrets.PUSHDECK_DEPLOY_URL }}
Treat the Deploy Hook URL as a secret. Anyone with this URL can trigger a deployment. Store it as a repository secret, never hardcode it in your workflow file. If it leaks, regenerate it immediately from the project overview.

Notifications

Open the Notifications tab on your project to be told when a deployment finishes. Two channels are available and you can use either or both.

Slack

Connect a Slack Incoming Webhook and Pushdeck posts a message to your chosen channel on every success or failure, with the commit, who triggered it, and the duration.

1

Go to api.slack.com/appsCreate New AppFrom scratch.

2

Name it (e.g. "Pushdeck") and pick your workspace.

3

Open Incoming Webhooks and turn it on.

4

Click Add New Webhook to Workspace and choose the channel for deploy updates.

5

Copy the URL (it starts with https://hooks.slack.com/) and paste it into the Notifications tab. Use Send a test notification to confirm.

One webhook posts to one channel. Want a different channel per project? Create a separate webhook for each and paste the matching URL into that project.

Webhook (custom integrations)

Point the Notification URL at any endpoint and Pushdeck sends an HTTP POST with a JSON body each time a deployment reaches a final state. The request carries the header User-Agent: Pushdeck-Webhook/1.0.

Events (the event field):

  • deployment.completed — a deployment finished. Read status for success or failed.
  • test — sent only when you click "Send a test notification".

Example payload:

{
  "event": "deployment.completed",
  "status": "success",                  // or "failed"
  "deployment": {
    "id": 123,
    "release": "20260402070456",
    "status": "success",
    "is_rollback": false,
    "started_at": "2026-04-02T07:04:38+00:00",
    "finished_at": "2026-04-02T07:04:56+00:00",
    "duration_seconds": 18,             // null if unknown
    "failure_reason": null,             // set when status is "failed"
    "failure_category": null,           // repository | server | hook | general
    "url": "https://app.pushdeck.dev/projects/1/deployments/123"
  },
  "commit": {
    "hash": "abc1234fde...",            // null if not captured
    "short_hash": "abc1234f",
    "message": "Fix login",
    "author": "Jane",
    "url": "https://github.com/owner/repo/commit/abc1234f"
  },
  "project": {
    "id": 1,
    "name": "My App",
    "repository": "owner/repo",
    "branch": "main"
  },
  "triggered_by": { "name": "Jane" }, // null for webhook/CI deploys
  "timestamp": "2026-04-02T07:04:56+00:00"
}
Notifications are best-effort: Pushdeck does not retry, and a non-2xx response from your endpoint never affects the deployment.

Team & Roles

Invite teammates to collaborate on a project from the Team tab (visible to the project owner). Access is scoped per project: someone can be an admin on one project and view-only on another.

Roles

RoleWhat they can do
OwnerThe creator. Full control, including team management and deleting the project. Cannot be removed.
AdminManage servers, repository, hooks, environment, settings, and notifications, and deploy or roll back. Cannot manage the team or delete the project.
DeployerDeploy and roll back, and manage notifications. No access to settings, servers, repository, or secrets.
ViewerRead-only: view the project and its deployment history. Can also manage notifications.

Beyond the role, you can fine-tune individual permissions per person (for example, grant a viewer the ability to manage hooks, or revoke notification access from someone who otherwise has it by default). Permissions can be set at invite time and changed later.

Invitations

1

On the Team tab, enter the person's email, pick a role, optionally customize permissions, and send the invitation.

2

They receive an email. If they do not have a Pushdeck account yet, they can create one straight from the link.

3

The project appears on their dashboard only after they explicitly accept. Until then it shows as a pending invitation on both sides.

Only the owner can manage the team. Members can leave a project at any time, and the owner is notified when they do. Secrets like the environment file and the deploy hook URL are never shown to members who lack the matching permission.

Security

  • SSH keys are generated by Pushdeck. You never share your own private keys. The private key is stored encrypted in the database and decrypted in memory only when a deployment job runs. The public key on your server can be deleted at any time to revoke access.
  • Repository access tokens are stored and used only for git clone operations. They are redacted from all log output.
  • Environment files are encrypted at rest and require your password to view or edit.
  • Deploy Hook URLs contain a random token. Regenerate from the project overview if one gets compromised. They are only shown to the owner and admins.
  • Team access is role-based and scoped per project. Members only see what their role and permissions allow; secrets such as the environment file and deploy hook URL are hidden from those without access.
  • Deployment jobs run with the same OS user as your SSH connection. Use Run As on hooks to run specific commands as a different user (e.g. www-data) via passwordless sudo.

Troubleshooting

Server shows "failed" after connection test

  • Verify the host/IP and port are reachable (nc -zv HOST PORT).
  • Confirm you added Pushdeck's public key to ~/.ssh/authorized_keys on the server for the correct user.
  • Check that the username in Pushdeck matches the user you added the key for.
  • Check that the user has read/write access to the deploy path: ls -la /var/www/myapp. If not, run sudo chown -R your-user:your-user /var/www/myapp.

Clone step fails

  • Authentication failed: your access token is invalid or expired. Update it under the Repository tab.
  • Repository not found: check the owner/repo spelling and that the token has read access.
  • Branch not found: verify the branch name exactly (it is case-sensitive).
  • Could not resolve host: the server has no internet access or DNS is not working.
  • git: command not found: install git on the server with sudo apt install git.
  • Permission denied: the SSH user does not have write access to the deploy path.

Hook step fails

  • Click the eye icon on that step to see the full SSH output and exact error.
  • All hook commands run from the release directory, so use relative paths.
  • If using Run As, the connecting user needs passwordless sudo configured for that target user.
  • Test your script manually by SSHing into the server and running it in the release directory.

Deploy button is disabled

  • All servers must show a connected badge. Click Test on each server.
  • A repository must be connected under the Repository tab.
  • A deployment may already be in progress. Wait for it to finish or fail.

A deployment step keeps failing

Pushdeck automates what you would do manually on your server. If a step fails, a good first check is to try the same thing yourself: SSH into the server, navigate to the same directory, and run the same command with the same user. If it fails there too, the issue is with the server configuration, not Pushdeck.

Common things to verify:

  • Access tokens (GitHub/GitLab) are valid and have not expired. Update them under the Repository tab.
  • The SSH user has the permissions needed to run the commands in your hooks.
  • Any external services your scripts call (package registries, databases) are reachable from the server.

If the same command works fine when you run it manually on the server but keeps failing through Pushdeck, open a Help request and share the step output. We are happy to help.