A quick run through of setting up a simple VPS for hosting sites.
| README.md | ||
VPS App Hosting
It's a basic setup running docker compose for each application, as well as Caddy which handles routing and TLS certs.
NOTE: The hardening and setup is designed for Debian.
Hardening
# Update things & reboot if you got kernal updates.
apt update && apt full-upgrade -y
# Create non-root user.
adduser deploy
usermod -aG sudo deploy
# Give user your ssh key. If you logged in with a key:
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
# If you logged in with a password:
ssh-copy-id deploy@YOUR_SERVER_IP
# Test user in new shell (keeping current one open).
ssh deploy@YOUR_SERVER_IP
sudo whoami
# Hardening ssh.
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
EOF
sudo systemctl reload ssh
# Test another shell login.
# Firewall.
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
# Fail2ban
sudo apt install -y fail2ban
# Automatic patch updates.
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Setup
# Clear any existing docker stuff.
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do
sudo apt remove -y $pkg
done
# Add Docker GPG keys and repo.
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine and Compose
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Run Docker as deploy user.
sudo usermod -aG docker $USER
# Verify.
docker run hello-world
docker compose version
# Then kill the hello-word container and image.
Structure
├── apps
│ ├── caddy
│ │ ├── caddy
│ │ │ └── Caddyfile
│ │ └── docker-compose.yml
│ ├── test
│ │ ├── docker-compose.yml
│ │ ├── Dockerfile
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── hello.templ
│ │ └── main.go
│ └── whoami
│ └── docker-compose.yml
└── README.md
Adding Apps
To add a new app:
- Create the app subdirectory (or from a
git clone ...). - Update
~apps/caddy/caddy/Caddyfilewith the new domain or subdomain and port. - Add the DNS record for the domain/subdomain to point to server IP.
- Check domain progress with
dig +short sub.domain.tldexpecting to see the IP. - Build and start the new app container
docker compose up -d - Run
cd ~/apps/caddy && docker compose up -d --force-recreate.
Or once the app code is there and DNS is in place:
cd ~/apps/new-app && docker compose up -d
docker compose -f ~/apps/caddy/docker-compose.yml exec caddy caddy reload --config /etc/caddy/Caddyfile
docker logs new-app -f --tail 30
Update App
Run docker compose pull && docker compose up -d to pull the latest image versions (if :latest tags are used).
Git (Forgejo)
Because I use git over ssh port 2222 is required, so it cannot run through Caddy. Instead I ran the following to expose it through the firewall.
sudo ufw allow 2222/tcp comment 'Forgejo git ssh'