Last active
April 29, 2025 13:39
-
-
Save kidGodzilla/aa0b80644b684db8bfd38b411016c888 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Then, for example, inside your project (where you've already added a git remote) you can: