Alpine Linux is the smallest image we offer. On our servers a fresh Alpine 3.21 used 55 MB of RAM and 153 MB of disk at idle, against 313 MB and 2.7 GB for Ubuntu 24.04. You get that by giving up the usual tools. Alpine uses apk instead of apt, doas instead of sudo, OpenRC instead of systemd, and musl instead of glibc.
The steps below are the same first ten minutes as on Debian or Ubuntu, translated to Alpine. We ran every command on our Alpine 3.21 image before publishing, and two of them caught us out the first time. Log in as root with the password from your dashboard.
1. Update, then reboot once
apk update && apk upgrade
uname -r
ls /lib/modules
Your server already installed available updates on its first boot, so apk upgrade usually has little left to do. Look at the last two lines instead. If that first-boot update brought a new kernel, the running kernel (uname -r) no longer has its modules on disk. Anything that loads a module then fails until you reboot. On our test server the firewall install printed ERROR: Couldn't determine iptables version. If the two versions differ, reboot now:
reboot
Our test server was back in under a minute.
2. Create a user that can log in
adduser -D deploy
passwd deploy
adduser deploy wheel
Don't skip passwd. adduser -D creates the account without a password, Alpine marks it as locked, and sshd refuses a locked account even with a correct key. In our test the only clue was this line in /var/log/messages:
User deploy not allowed because account is locked
The password also matters later: it's what doas will ask for.
If you added an SSH key when you ordered the server, it is in root's authorized_keys. Copy it to the new user:
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
No key yet? Create one on your computer and paste the public half into /home/deploy/.ssh/authorized_keys.
3. Make doas ask for a password
Alpine uses doas instead of sudo, and our image already allows the wheel group to use it. The rule in /etc/doas.d/wheel.conf is permit nopass :wheel, so any member of wheel becomes root without typing anything. Make it ask for the password, then remember it for a few minutes:
echo 'permit persist :wheel' > /etc/doas.d/wheel.conf
From now on doas apk add nano works the way sudo apt install nano does on Ubuntu.
4. SSH: keys only, no root
sshd reads the drop-in files in /etc/ssh/sshd_config.d/ in name order and keeps the first value it finds for each setting. On our image the first file is 00-eqvps.conf, which allows root and password logins so you can get in on day one. Replace its contents:
cat > /etc/ssh/sshd_config.d/00-eqvps.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
EOF
sshd -t && rc-service sshd restart
sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication) '
The third line matters on Alpine. The image runs the PAM build of OpenSSH, and with only the first two lines our test server still offered keyboard-interactive login. With all three, a login attempt without a key gets:
Permission denied (publickey).
Keep your root session open. In a second terminal, check that ssh deploy@YOUR.SERVER.IP works (on a NAT plan, add -p and the personal port from your dashboard) and that doas id accepts the password.
5. Firewall: ufw
ufw is in the community repository, which is enabled on our image:
apk add ufw
ufw limit 22/tcp
ufw --force enable
rc-update add ufw default
ufw status
limit allows SSH but slows down anyone opening many connections in a row. rc-update matters here, because OpenRC doesn't start the firewall after a reboot unless you add it to the runlevel. After a reboot our server came back with Status: active. On a NAT plan leave SSH on port 22: your personal external port is forwarded to 22 inside the server. On a plan with its own IPv4, open web ports when you need them (ufw allow 80/tcp, ufw allow 443/tcp). The ufw guide covers the rest.
6. Switch on cron and daily updates
A fresh Alpine server doesn't run cron at all. Turn it on:
rc-update add crond default
rc-service crond start
Alpine's root crontab already runs everything in /etc/periodic/daily at 02:00. Drop an update script there:
cat > /etc/periodic/daily/apk-upgrade <<'EOF'
#!/bin/sh
apk upgrade --update-cache --no-progress >> /var/log/apk-auto.log 2>&1
EOF
chmod +x /etc/periodic/daily/apk-upgrade
run-parts --test /etc/periodic/daily
The last command lists the scripts cron will run, and yours should be on the list. This keeps you on the fixes for 3.21. Moving to the next Alpine release is a separate, manual step. Kernel updates still need a reboot: repeat the uname -r / ls /lib/modules check from step 1 now and then.
7. OpenRC in one table
If you know systemd, this is most of what changes:
| Task | systemd | Alpine (OpenRC) |
|---|---|---|
| Start a service | systemctl start nginx | rc-service nginx start |
| Start at boot | systemctl enable nginx | rc-update add nginx default |
| Status | systemctl status nginx | rc-service nginx status |
| What's running | systemctl list-units | rc-status |
| System log | journalctl | /var/log/messages |
8. Docker, and the port ufw doesn't block
Alpine makes a good Docker host because the system itself stays out of the way:
apk add docker docker-cli-compose
rc-update add docker default
rc-service docker start
docker run --rm hello-world
On our server this took 12 seconds including the download. With the Docker daemon running, used memory went from 57 MB to 124 MB.
One trap that isn't specific to Alpine: Docker writes its own firewall rules. With ufw allowing only SSH, we started nginx with -p 8080:80 and reached it from outside the server on port 8080. ufw didn't block it. If a container should only be reached through a reverse proxy on the same server, publish it on localhost:
docker run -d -p 127.0.0.1:8080:80 nginx:alpine
Which plan
Alpine leaves almost all of the RAM to your own software. The Nano plan (2 vCPU, 1 GB, 15 GB, $3/mo) is enough for a small service or a few containers. It's a NAT plan, so SSH comes in through a personal port. If you need ports 80 and 443 open to the internet, Nano-IP has its own IPv4 for $8/mo. For heavier Docker stacks, Micro gives you 2 GB for $5/mo.
Alpine 3.21 is on the Alpine VPS page and available on every Linux plan in VPS plans. Payment is in USDT, USDC or ETH on Base, Ethereum or Polygon, and no ID or card is needed.
Comments
No comments yet. Be the first.