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 |
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
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
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
sudo su
before running the script:Don't have a Public / Private Keypair?
Type the following command to generate a new SSH key pair:
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
andid_ed25519.pub
in your~/.ssh/
directory.Next, copy this key to your server.
Before running
server_bootstrap.sh
, test SSH without a password to ensure you still have access to the server via Public/Private Keypair: