Skip to content

Instantly share code, notes, and snippets.

@kidGodzilla
Last active April 29, 2025 13:39
Show Gist options
  • Save kidGodzilla/aa0b80644b684db8bfd38b411016c888 to your computer and use it in GitHub Desktop.
Save kidGodzilla/aa0b80644b684db8bfd38b411016c888 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -euo pipefail
#####################################################################################
# This Bootstrap Script installs Dokku latest on Ubuntu (use LTS or latest)
#
# This script also installs UFW (firewall), some basic Dokku plugins, and
# raises ulimits. Comment out any step you wish to skip in main.
#
# IMPORTANT: This script also disables password authentication via SSH for
# subsequent logins (a recommended hardening step). Don't forget to add your SSH
# key to your server before logging out! (Script will require it)
#####################################################################################
# See Comments in the related GitHub Gist below for installation instructions
#####################################################################################
# DOKKU_TAG=v0.35.18
#--------------------------------------------------------------------
# 0. Privilege check
#--------------------------------------------------------------------
check_root() {
if [ "$USER" != "root" ]; then
echo "Permission Denied"
echo "Can only be run by root"
exit
fi
}
#--------------------------------------------------------------------
# 1. Core packages & updates
#--------------------------------------------------------------------
install_core_packages() {
apt-get update
apt-get install -y curl wget git
}
automatic_updates() {
apt-get install -y --no-install-recommends \
unattended-upgrades apt-listchanges bsd-mailx
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure --priority=low unattended-upgrades
echo 'Unattended-Upgrade::Automatic-Reboot "true";' \
>/etc/apt/apt.conf.d/50unattended-upgrades
# run the dry-run only if the cache lock is free
( flock -w 0 9 || exit 0
unattended-upgrades --dry-run --debug
) 9>/var/lib/dpkg/lock
}
#--------------------------------------------------------------------
# 2. SSH keys & hardening
#--------------------------------------------------------------------
create_keys_file() { mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys; }
check_ssh_keys_present() {
if [ ! -s ~/.ssh/authorized_keys ]; then
echo "No SSH keys in ~/.ssh/authorized_keys. Add one before running."
exit 1
fi
}
disable_password_authentication() {
sed -i "/^[^#]*ChallengeResponseAuthentication[[:space:]]yes.*/c\ChallengeResponseAuthentication no" /etc/ssh/sshd_config || true
sed -i "/^[^#]*PasswordAuthentication[[:space:]]yes/c\PasswordAuthentication no" /etc/ssh/sshd_config || true
reload_ssh
}
disable_root_ssh() {
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
reload_ssh
}
#--------------------------------------------------------------------
# 3. ulimits
#--------------------------------------------------------------------
raise_ulimits() {
if ! grep -q "fs.file-max = 65535" "/etc/sysctl.conf"; then
echo "fs.file-max = 65535" >> /etc/sysctl.conf
echo "fs.nr_open = 65535" >> /etc/sysctl.conf
echo "session required pam_limits.so" >> /etc/pam.d/common-session
cat >>/etc/security/limits.conf <<'EOF'
* soft nproc 65535
* hard nproc 65535
* soft nofile 65535
* hard nofile 65535
root soft nproc 65535
root hard nproc 65535
root soft nofile 65535
root hard nofile 65535
EOF
ulimit -n 65535
fi
}
reload_ssh() {
# try the common Ubuntu/Debian unit name, then the Red Hat-style one,
# then fall back to the SysV init script (harmless if absent)
systemctl reload ssh 2>/dev/null \
|| systemctl reload sshd 2>/dev/null \
|| service ssh reload 2>/dev/null \
|| service sshd reload 2>/dev/null \
|| true
}
#--------------------------------------------------------------------
# 4. Firewall & Fail2Ban
#--------------------------------------------------------------------
install_firewall() {
apt-get install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable
}
install_fail2ban() {
# suppress byte-compile warnings for all Python packages
export PYTHONWARNINGS=ignore::SyntaxWarning
apt-get install -y fail2ban
ln -sf /etc/fail2ban/jail.d/defaults-debian.conf /etc/fail2ban/jail.d/sshd.local
systemctl restart fail2ban
}
#--------------------------------------------------------------------
# 5. Journald & Docker housekeeping
#--------------------------------------------------------------------
persist_journald() {
sed -i 's/^#\?Storage=.*/Storage=persistent/' /etc/systemd/journald.conf
systemctl restart systemd-journald
}
docker_prune_cron() {
echo '@daily root docker system prune -af' >/etc/cron.d/docker-prune
}
#--------------------------------------------------------------------
# 6. Swap & time sync
#--------------------------------------------------------------------
create_swap_if_needed() {
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
if (( mem_total < 8000000 )) && [ ! -f /swapfile ]; then
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
}
install_chrony() {
apt-get install -y chrony
systemctl enable --now chrony
}
#--------------------------------------------------------------------
# 7. Hostname
#--------------------------------------------------------------------
set_hostname() {
current=$(hostname)
if [[ "$current" == "localhost" || "$current" == ip-* ]]; then
hostnamectl set-hostname "dokku-$(hostname -I | awk '{print $1}' | tr . -)"
fi
}
#--------------------------------------------------------------------
# 8. Dokku install
#--------------------------------------------------------------------
install_dokku() {
command -v dokku &>/dev/null && return
wget -q https://dokku.com/bootstrap.sh
bash bootstrap.sh
}
ensure_dokku() { command -v dokku &>/dev/null || { echo "dokku install failed"; exit 1; }; }
#--------------------------------------------------------------------
# 9. Convenience dirs
#--------------------------------------------------------------------
make_dirs() { mkdir -p ~/dumps/{postgres,mysql,redis,mongo}; }
#--------------------------------------------------------------------
# 10. Dokku plugins
#--------------------------------------------------------------------
install_redis() { dokku plugin:installed redis || dokku plugin:install https://github.com/dokku/dokku-redis.git redis; }
install_postgres() { dokku plugin:installed postgres || dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres; }
install_mysql() { dokku plugin:installed mysql || dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql; }
install_mongo() { dokku plugin:installed mongo || dokku plugin:install https://github.com/dokku/dokku-mongo.git mongo; }
install_memcached() { dokku plugin:installed memcached || dokku plugin:install https://github.com/dokku/dokku-memcached.git memcached; }
install_clickhouse() { dokku plugin:installed clickhouse || dokku plugin:install https://github.com/dokku/dokku-clickhouse.git clickhouse; }
install_letsencrypt() {
[ -d /var/lib/dokku/plugins/available/letsencrypt ] || {
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
dokku letsencrypt:cron-job --add
}
}
install_limited_users() {
[ -d /var/lib/dokku/plugins/available/limited-users ] || \
dokku plugin:install https://github.com/kidGodzilla/dokku-limited-users.git
}
#--------------------------------------------------------------------
# 11. MAIN
#--------------------------------------------------------------------
main() {
check_root
install_core_packages
create_keys_file
check_ssh_keys_present
disable_password_authentication
disable_root_ssh
install_firewall
install_fail2ban
persist_journald
install_chrony
set_hostname
raise_ulimits
create_swap_if_needed
install_dokku
ensure_dokku
make_dirs
install_redis
install_postgres
install_mysql
install_mongo
install_memcached
install_clickhouse
install_letsencrypt
install_limited_users
docker_prune_cron
automatic_updates
}
main
@kidGodzilla
Copy link
Author

kidGodzilla commented Apr 5, 2024

What is Dokku?

Dokku gives you all the tools you need to manage Docker like any other PaaS you've used (Heroku, Vercel, etc). But instead of paying a monthly fee, you own it, and the servers your code run on.

Since it's powered by Docker, it already has plugins to create and manage any database you could imagine, and you can run pretty much anything.

I like to recommend it because it takes less than 5 minutes to learn how it works, and within a few days you'll be an expert. No black box / mystery, it's all open source and very simple.

Installing

  1. Install Ubuntu Latest on a VPS or dedicated server from DigitalOcean, OVH, etc.
  2. SSH into your server.
  3. Add your keys to your server (Password login to SSH will be disabled for security purposes). See "Don't have a Public / Private Keypair?" below for more details.
  4. You might want to sudo su before running the script:
sudo su
wget https://gist.githubusercontent.com/kidGodzilla/aa0b80644b684db8bfd38b411016c888/raw/fad94f0ec25cde394db4ab0445f1506bcfeb140f/server_bootstrap.sh

bash ./server_bootstrap.sh
  1. Add your public key to Dokku (for deploys)
echo "YOUR_PUBLIC_KEY_HERE" | dokku ssh-keys:add admin

Don't have a Public / Private Keypair?

Type the following command to generate a new SSH key pair:

ssh-keygen -t ed25519

You can choose the default options, and you do not need to create a passphrase.

This will generate two files, by default called id_ed25519 and id_ed25519.pub in your ~/.ssh/ directory.

Next, copy this key to your server.

ssh-copy-id [email protected]

Before running server_bootstrap.sh, test SSH without a password to ensure you still have access to the server via Public/Private Keypair:

@kidGodzilla
Copy link
Author

kidGodzilla commented Apr 5, 2024

Using Dokku

Creating an App (on your Dokku server)

dokku apps:create appname

Add domains to your App (on your Dokku server)

dokku domains:add appname domainname.com domainname2.com sub.domainname.com

Deploying your App

# Add the remote before your first push
git remote add dokku [email protected]:appname

# Use this command every time you push new changes, to deploy
git push dokku main:master

@kidGodzilla
Copy link
Author

kidGodzilla commented Apr 5, 2024

Optional (but recommended)

Install Dokku CLI locally (macOS)

This lets you interact with your Dokku server(s) from the command line of your local repository, so you can view logs, update domains, restart your app, etc. without SSH into your server.

brew install dokku/repo/dokku

Then, for example, inside your project (where you've already added a git remote) you can:

dokku domains:report # list domains
dokku domains:add domainname.com # add a domain
dokku logs # view logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment