📚 Docs

Deploy your first app, step by step

From an empty directory to a live URL on the PaaS. We build a tiny Node.js service — the same shape as the sample apps that ship with the platform — then configure it, give it a database, and finish with logs and HTTPS. No prior platform knowledge needed; every step says what it does and what you should see.

Prerequisites

Step 0 — Set up the deploy key

chmod 600 ~/.ssh/dokku-deploy.pem

Optional but recommended — teach SSH about the key once:

cat >> ~/.ssh/config <<'EOF'
Host dokku-paas
  HostName <public-ip>
  User dokku
  IdentityFile ~/.ssh/dokku-deploy.pem
EOF

The rest of this guide uses the dokku-paas alias. Without it, add -i ~/.ssh/dokku-deploy.pem to every ssh command and use dokku@<public-ip> instead of dokku-paas in the git remote.

Step 1 — Scaffold the app

mkdir my-first-app && cd my-first-app

server.js — a dependency-free HTTP service with a /health endpoint. Note the two platform contracts: it reads PORT from the environment and it reads its config (GREETING) from the environment too.

'use strict';

const http = require('http');

const APP_NAME = 'my-first-app';
const PORT = process.env.PORT || 5000;
const GREETING = process.env.GREETING || 'Hello from the PaaS!';

const server = http.createServer((req, res) => {
  if (req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok', app: APP_NAME }) + '\n');
    return;
  }
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(
    JSON.stringify({
      app: APP_NAME,
      message: GREETING,
      database: process.env.DATABASE_URL ? 'linked' : 'not linked',
    }) + '\n'
  );
});

server.listen(PORT, () => {
  console.log(`${APP_NAME} listening on port ${PORT}`);
});

package.json — the manifest that tells the platform this is a Node.js app:

{
  "name": "my-first-app",
  "version": "1.0.0",
  "private": true,
  "main": "server.js",
  "scripts": { "start": "node server.js" }
}

Procfile — declares the web process the platform runs:

web: node server.js

.gitignore:

node_modules/

Step 2 — Test it locally

node server.js &          # starts on port 5000
server_pid=$!
curl http://localhost:5000/
curl http://localhost:5000/health
kill "$server_pid"

You should see the JSON greeting and {"status":"ok","app":"my-first-app"}. If it works locally, it satisfies the platform's build requirements.

Step 3 — Commit it

git init -b main
git add -A
git commit -m "Initial commit"

Step 4 — Create the app on the platform

ssh dokku-paas apps:create my-first-app

The name must match ^[a-z0-9][a-z0-9.-]*$ (lowercase, digits, dots, hyphens — no underscores). Creating the app reserves the name and its URL; nothing runs yet.

Step 5 — Push and deploy

git remote add dokku dokku-paas:my-first-app
git push dokku main:master

Why main:master? The platform deploys only its master branch. Your local branch is main, so push it across. If you forget the :master, the push succeeds but nothing deploys (the output warns you).

Watch the output — it is the whole deploy pipeline:

  1. -----> Cleaning up / -----> Building — the Node.js buildpack is detected and your app is built into a container image
  2. -----> Releasing — the new container starts
  3. -----> Deploying — zero-downtime checks: the platform confirms the new container is up before switching traffic to it
  4. -----> Deployed! followed by the app's URL

Step 6 — Verify it's live

curl https://my-first-app.prototype.imeme.name/
curl https://my-first-app.prototype.imeme.name/health

Expect the same JSON as locally. The post-deploy hook automatically enables Let's Encrypt for every public app, and cron renews certificates. Custom DNS must already be configured; wildcard DNS is not created by the app deploy. Certificate failures are explicit: inspect deploy output, and have the operator fix DNS/port 80 and retry as in step 10. Never claim HTTPS is ready without a successful request and valid certificate.

Step 7 — Change configuration without touching code

ssh dokku-paas config:set my-first-app GREETING="Hello, configured world"
curl https://my-first-app.prototype.imeme.name/

config:set restarts the app with the new environment — the greeting changes with no commit and no push. Avoid full config dumps: they reveal secrets. The platform automatically provisions a private Open Terminal API URL and shared key into every app. These are sensitive and do not create per-app API isolation; don't print them in logs or HTTP responses. See Web shell for the trust boundary.

Step 8 — Give it a database

ssh dokku-paas postgres:link demo-db my-first-app
curl https://my-first-app.prototype.imeme.name/

Linking injects DATABASE_URL into the app's environment and restarts it — the response's database field flips to linked. The same works for Redis: ssh dokku-paas redis:link demo-cache my-first-app injects REDIS_URL. Unlink with postgres:unlink demo-db my-first-app.

Your code never hardcodes a host, port, or password: it parses DATABASE_URL, so the same artifact runs anywhere.

Step 9 — Read its logs and manage the process

ssh dokku-paas logs my-first-app          # recent logs
ssh dokku-paas logs my-first-app -t       # stream live (Ctrl-C to stop)
ssh dokku-paas ps:report my-first-app     # process state
ssh dokku-paas ps:restart my-first-app    # restart without a deploy

Everything the app prints to stdout/stderr shows up in logs.

Step 10 — Verify automatic HTTPS; retry if needed (operator step)

HTTPS uses a Let's Encrypt certificate, automatically enabled by the post-deploy hook for every public app, not just preinstalled apps. If issuance fails, the operator fixes DNS and port-80 reachability, then runs:

sudo paas-enable-https my-first-app    # run on the host, not in the terminal

After successful issuance, verify https://my-first-app.prototype.imeme.name and the certificate renews itself automatically (the platform runs a renewal job daily). Your app changed nothing — it still serves plain HTTP internally; TLS terminates at the platform edge.

Step 11 — Ship a change

# edit server.js, then:
git add -A && git commit -m "Change the greeting"
git push dokku main:master

Each push is a zero-downtime deploy: the new version is built and verified before traffic switches, and the old version drains for 60 seconds before retiring. If the new version crashes on boot, the deploy fails and the old version keeps serving.

Troubleshooting

Symptom Likely cause Fix
Please specify a valid app name / create fails uppercase, underscores, or a leading dot/hyphen in the name rename to match ^[a-z0-9][a-z0-9.-]*$
Push succeeds but the site never changes pushed to main, not the deploy branch git push dokku main:master
Push asks for a password / Permission denied (publickey) deploy key not used ssh-add ~/.ssh/dokku-deploy.pem, the ~/.ssh/config alias from step 0, or GIT_SSH_COMMAND="ssh -i ~/.ssh/dokku-deploy.pem" git push …
ssh/push times out firewall allowlist, wrong IP or SSH unavailable verify the target and ask your operator to check your CIDR
URL returns 502 Bad Gateway app crashed or isn't listening on $PORT / bound to 127.0.0.1 ssh dokku-paas logs my-first-app; fix the bind to 0.0.0.0:$PORT
Deploy fails at the checks step app exits within ~10 s of boot (crashes on start) read the push output and logs; fix the boot crash and re-push
App can't reach Postgres/Redis service not linked postgres:link demo-db my-first-app / redis:link demo-cache my-first-app

Where to go next