Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save johan--/b2523ef77dd92bd963417652c20fece9 to your computer and use it in GitHub Desktop.

Select an option

Save johan--/b2523ef77dd92bd963417652c20fece9 to your computer and use it in GitHub Desktop.
Set up a Ubuntu server to deploy Kamal 2.x Docker containers to, hardened security and production ready
#!/bin/bash
# Production Docker Host Hardening Script
# Suitable for both Kamal deployment and builder hosts
# For Ubuntu Server 24.04 LTS
set -euo pipefail
IFS=$'\n\t'
# --- Aesthetics and Error Handling ---
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
ALIEN='\xF0\x9F\x91\xBD'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${ALIEN} ${message}${NC}"
}
handle_error() {
local line_number=$1
print_message "${RED}" "Error on line ${line_number}"
exit 1
}
trap 'handle_error ${LINENO}' ERR
# Check if script is run as root
if [[ $EUID -ne 0 ]]; then
print_message "${RED}" "This script must be run as root"
exit 1
fi
# --- System Updates and Essential Packages ---
print_message "${YELLOW}" "Updating system packages..."
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
print_message "${YELLOW}" "Installing essential packages..."
DEBIAN_FRONTEND=noninteractive apt-get install -y \
ufw \
fail2ban \
curl \
wget \
gnupg \
lsb-release \
ca-certificates \
apt-transport-https \
software-properties-common \
sysstat \
auditd \
audispd-plugins \
unattended-upgrades \
acl \
openssh-server \
apparmor \
apparmor-utils \
chrony \
aide \
rkhunter \
logwatch \
git
# --- System Hardening ---
print_message "${YELLOW}" "Configuring system security..."
# AppArmor
systemctl enable apparmor
systemctl start apparmor
# AIDE initialization
aideinit
# Configure chrony for secure time sync
cat <<EOF > /etc/chrony/chrony.conf
pool pool.ntp.org iburst maxsources 4
keyfile /etc/chrony/chrony.keys
driftfile /var/lib/chrony/chrony.drift
logdir /var/log/chrony
maxupdateskew 100.0
rtcsync
makestep 1 3
leapsectz right/UTC
EOF
# Configure system limits
cat <<EOF > /etc/security/limits.d/docker.conf
* soft nproc 10000
* hard nproc 10000
* soft nofile 1048576
* hard nofile 1048576
* soft core 0
* hard core 0
* soft stack 8192
* hard stack 8192
EOF
# Kernel hardening
cat <<EOF > /etc/sysctl.d/99-security.conf
# Network security
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
# Docker needs IPv4 forwarding
net.ipv4.ip_forward = 1
# System limits
fs.file-max = 1048576
kernel.pid_max = 65536
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_reuse = 1
vm.max_map_count = 262144
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.perf_event_paranoid = 3
kernel.unprivileged_bpf_disabled = 1
net.core.bpf_jit_harden = 2
kernel.yama.ptrace_scope = 2
# File system hardening
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.suid_dumpable = 0
# Additional network hardening
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
EOF
sysctl -p /etc/sysctl.d/99-security.conf
# --- Docker Installation and Configuration ---
print_message "${YELLOW}" "Installing Docker..."
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
rm get-docker.sh
print_message "${YELLOW}" "Configuring Docker..."
mkdir -p /etc/docker
cat <<EOF > /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"icc": false,
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"userns-remap": "default",
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 64000,
"Soft": 64000
}
},
"features": {
"buildkit": true
},
"experimental": false,
"default-runtime": "runc",
"seccomp-profile": "/etc/docker/seccomp.json",
"selinux-enabled": true,
"userns-remap": "default",
"storage-driver": "overlay2",
"storage-opts": ["overlay2.override_kernel_check=true"],
"metrics-addr": "127.0.0.1:9323",
"builder": {
"gc": {
"enabled": true,
"defaultKeepStorage": "20GB"
}
}
}
EOF
# --- User Setup ---
print_message "${YELLOW}" "Creating docker user..."
adduser --system --group --shell /bin/bash --home /home/docker --disabled-password docker
usermod -aG docker docker
# --- SSH Hardening ---
print_message "${YELLOW}" "Configuring SSH..."
mkdir -p /home/docker/.ssh
chmod 700 /home/docker/.ssh
cat <<EOF > /etc/ssh/sshd_config
Include /etc/ssh/sshd_config.d/*.conf
Port 22
AddressFamily inet
Protocol 2
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_rsa_key
SyslogFacility AUTH
LogLevel VERBOSE
LoginGraceTime 30
PermitRootLogin prohibit-password
StrictModes yes
MaxAuthTries 3
MaxSessions 5
PubkeyAuthentication yes
HostbasedAuthentication no
IgnoreRhosts yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
AllowAgentForwarding no
AllowTcpForwarding yes # Required for Docker forwarding
X11Forwarding no
PermitTTY yes
PrintMotd no
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no
AllowUsers docker root
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
Subsystem sftp internal-sftp
EOF
# --- Firewall Configuration ---
print_message "${YELLOW}" "Configuring firewall..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable
# --- Fail2ban Configuration ---
print_message "${YELLOW}" "Configuring fail2ban..."
cat <<EOF > /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = ufw
banaction_allports = ufw
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
[docker]
enabled = true
filter = docker
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
[docker-api]
enabled = true
port = 2375,2376
filter = docker-api
logpath = /var/log/docker.log
maxretry = 3
bantime = 3600
EOF
# Create custom Docker fail2ban filter
cat <<EOF > /etc/fail2ban/filter.d/docker.conf
[Definition]
failregex = failed login attempt from <HOST>
ignoreregex =
EOF
# --- Logging and Monitoring ---
print_message "${YELLOW}" "Configuring logging and monitoring..."
# Docker log rotation
cat <<EOF > /etc/logrotate.d/docker
/var/lib/docker/containers/*/*.log {
rotate 7
daily
compress
missingok
delaycompress
copytruncate
size 100M
}
EOF
# Configure auditd
cat <<EOF > /etc/audit/rules.d/docker.rules
# Docker daemon configuration
-w /usr/bin/dockerd -k docker
-w /var/lib/docker -k docker
-w /etc/docker -k docker
-w /usr/lib/systemd/system/docker.service -k docker
-w /etc/systemd/system/docker.service -k docker
-w /usr/lib/systemd/system/docker.socket -k docker
-w /etc/default/docker -k docker
-w /etc/docker/daemon.json -k docker
EOF
# --- Automatic Updates ---
print_message "${YELLOW}" "Configuring automatic updates..."
cat <<EOF > /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
"\${distro_id}:\${distro_codename}";
"\${distro_id}:\${distro_codename}-security";
"\${distro_id}ESMApps:\${distro_codename}-apps-security";
"\${distro_id}ESM:\${distro_codename}-infra-security";
};
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::InstallOnShutdown "false";
EOF
# --- System Maintenance ---
print_message "${YELLOW}" "Setting up maintenance tasks..."
cat <<EOF > /etc/cron.daily/docker-cleanup
#!/bin/bash
docker system prune -af --volumes
docker builder prune -af --keep-storage=20GB
EOF
chmod +x /etc/cron.daily/docker-cleanup
# Enable and start services
systemctl enable docker fail2ban auditd chronyd
systemctl restart docker fail2ban auditd chronyd
# Final cleanup
apt-get autoremove -y
apt-get clean
print_message "${GREEN}" "Setup complete! System hardening successful."
print_message "${YELLOW}" "Important next steps:"
print_message "${YELLOW}" "1. Add your SSH public key to /home/docker/.ssh/authorized_keys"
print_message "${YELLOW}" "2. Configure your deployment tools (Kamal, etc.)"
print_message "${YELLOW}" "3. REBOOT THE SYSTEM to apply all security settings"
print_message "${YELLOW}" "4. Review logs in /var/log/ regularly"
print_message "${YELLOW}" "5. Set up external monitoring and alerting"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment