Git-Based Docker Deployments on VPS for Hostperl Users

Use Git as the deployment source, not the server clipboard
Git-based Docker deployments on VPS work better than one-off file uploads when you need repeatable launches, fast rollbacks, and a migration path you can trust. If you run client sites, internal tools, or small SaaS apps on Hostperl, you already know the common failure points: a build that works on one machine but not another, an environment variable that disappears after reboot, or a deployment nobody can reproduce six weeks later. This guide walks you from the first SSH login to a working Docker Compose setup with Nginx in front, systemd keeping it alive, and a rollback path that does not depend on guesswork.
If you are choosing a VPS for this kind of work, Hostperl VPS gives you the control you need for Docker, reverse proxies, and app isolation without forcing you into a panel workflow that does not fit your stack.
This tutorial uses one documentation IP, 203.0.113.10, for every command example. Replace it with the real public IP assigned to your Hostperl server.
Connect, identify the OS, and create a sudo user
On your local computer:
ssh root@203.0.113.10That address is reserved for documentation. Use your server’s real IP instead. Keep the root session open until the new user login works.
If your provider gave you a custom SSH port, the first connection would look like this:
ssh -p 2222 root@203.0.113.10Now check the operating system before you install anything.
On the VPS as root:
cat /etc/os-releaseYou should see either Ubuntu/Debian or AlmaLinux/Rocky Linux. The next steps differ slightly.
Ubuntu and Debian
On the VPS as root:
apt update
apt install -y sudo adduser openssh-server ca-certificates curl gnupgCreate the non-root admin account and add it to the sudo group.
adduser deploy
usermod -aG sudo deploySet up SSH keys for the new account. Replace the key content with your own public key.
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cat > /home/deploy/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyChangeThisForYourServer deploy@example
EOF
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.sshOpen a second terminal now and test the new login before you change any root access settings.
ssh deploy@203.0.113.10
sudo -vYou should log in cleanly and get a sudo password prompt or confirmation. Do not continue until that works.
AlmaLinux and Rocky Linux
On the VPS as root:
dnf install -y sudo shadow-utils openssh-server ca-certificates curlCreate the non-root admin account and grant wheel access.
useradd -m -s /bin/bash deploy
passwd deploy
usermod -aG wheel deploySet up SSH keys safely.
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cat > /home/deploy/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyChangeThisForYourServer deploy@example
EOF
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.sshTest from a second terminal before you touch root login settings.
ssh deploy@203.0.113.10
sudo -vFor a Hostperl-managed VPS, this is also the point where support tickets get easier. Once you have a known admin user and key-based SSH, migrations and app cutovers become much cleaner.
Install Docker and Docker Compose cleanly
Use current Docker packages from Docker’s repository instead of older distro builds. That keeps the Engine and Compose plugin aligned.
Ubuntu and Debian
On the VPS as the non-root sudo user:
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
. /etc/os-release
printf 'deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu %s stable\n' "$VERSION_CODENAME" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCheck the version and enable Docker at boot.
docker --version
docker compose version
sudo systemctl enable --now dockerAlmaLinux and Rocky Linux
On the VPS as the non-root sudo user:
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now dockerConfirm the install.
docker --version
docker compose version
sudo systemctl status docker --no-pagerIf you want a deeper look at safe container checks after deployment, Hostperl has a related guide on Docker health checks for VPS deployments in 2026.
Prepare the application directory and clone the repo
Use one directory for the app, one for persistent data, and one for logs. That keeps restores much simpler.
On the VPS as the non-root sudo user:
sudo mkdir -p /opt/myapp/{app,data,logs}
sudo chown -R deploy:deploy /opt/myapp
cd /opt/myapp
pwdClone your Git repository. Replace the example URL with your real repository.
git clone https://github.com/example/example-app.git app
cd /opt/myapp/app
git rev-parse --short HEADYou should see a commit hash. That gives you a clear deployment anchor for rollbacks.
Write the Docker Compose stack and environment file
This example runs a simple app container on port 8000 behind Nginx. Change the image name and ports to match your application, but keep the structure.
On the VPS as the non-root sudo user:
cd /opt/myapp/app
nano compose.yamlPaste this content, then save and exit with Ctrl+O, Enter, and Ctrl+X.
services:
web:
image: ghcr.io/example/example-app:latest
container_name: example-app-web
restart: unless-stopped
env_file:
- /opt/myapp/app/.env
ports:
- "127.0.0.1:8000:8000"
volumes:
- /opt/myapp/data:/data
- /opt/myapp/logs:/var/log/example-app
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3Create the environment file with restrictive permissions.
nano /opt/myapp/app/.env
chmod 600 /opt/myapp/app/.envExample contents:
APP_ENV=production
APP_PORT=8000
APP_SECRET=change-this-secretThat secret file should stay owned by deploy and readable only by that user.
Run the stack and lock it into systemd
Start the app through Compose first. If the image or env file is wrong, you want the failure to happen here, not after a reboot.
On the VPS as the non-root sudo user:
cd /opt/myapp/app
docker compose pull
docker compose up -d
docker compose psYou should see the service in a healthy or running state. If the app exposes a health endpoint, check it from inside the VPS.
curl -i http://127.0.0.1:8000/healthNow create a systemd unit so Docker Compose starts at boot in a predictable way.
sudo nano /etc/systemd/system/myapp-compose.servicePaste this file and save it.
[Unit]
Description=MyApp Docker Compose Service
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp/app
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetTest the unit before you enable it.
sudo systemctl daemon-reload
sudo systemctl start myapp-compose.service
sudo systemctl status myapp-compose.service --no-pager
sudo systemctl enable myapp-compose.serviceIf you want a Git-first workflow with stronger change control, Hostperl also publishes Docker app deployment on VPS and Git-based Docker app deployment on VPS in 2026.
Put Nginx in front and open the firewall
Nginx listens on 80 and 443, then forwards requests to the container on 127.0.0.1:8000. That keeps the app private on the server.
Ubuntu and Debian
On the VPS as the non-root sudo user:
sudo apt install -y nginx
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw --force enableCreate the site file.
sudo nano /etc/nginx/sites-available/myappUse this configuration, then save and exit.
server {
listen 80;
server_name server.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enable the site and test syntax before you reload.
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx --no-pagerAlmaLinux and Rocky Linux
On the VPS as the non-root sudo user:
sudo dnf install -y nginx firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo systemctl enable --now nginxCreate the Nginx server block.
sudo nano /etc/nginx/conf.d/myapp.confUse this file content.
server {
listen 80;
server_name server.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Check syntax and reload.
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status nginx --no-pagerIf you need a broader comparison of web server choices for VPS workloads, see Nginx vs Apache for VPS hosting in 2026.
Add HTTPS with Let’s Encrypt
Once DNS points to the VPS, request a certificate. Replace server.example.com with your real hostname and make sure the DNS A record already resolves to the server.
Ubuntu and Debian
On the VPS as the non-root sudo user:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d server.example.comCertbot will update the Nginx config and ask whether to redirect HTTP to HTTPS. Choose redirect if this is the public site.
AlmaLinux and Rocky Linux
On the VPS as the non-root sudo user:
sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d server.example.comConfirm automatic renewal is scheduled.
sudo systemctl list-timers | grep certbot || true
sudo certbot renew --dry-runVerify the deployment from server and client
Do not stop at “the container is running.” Verify the full chain: Docker, systemd, Nginx, HTTP, and a reboot.
On the VPS as the non-root sudo user:
docker ps
sudo systemctl status myapp-compose.service --no-pager
sudo ss -ltnp | grep -E '(:80|:443|:8000)'
journalctl -u myapp-compose.service -n 50 --no-pager
journalctl -u nginx -n 50 --no-pagerOn your local computer:
curl -I https://server.example.com
curl https://server.example.com/healthYou should see a successful HTTP response and the app’s health payload or status code.
On the VPS as root, only after the new login is verified and the site works:
rebootAfter the server returns, reconnect and confirm everything survived the restart.
ssh deploy@203.0.113.10
docker ps
systemctl status docker myapp-compose nginx --no-pagerRollback, troubleshooting, and common failures
Most deployment problems fall into a few predictable buckets. Check the command that matches the symptom.
- Nginx fails to reload: run
sudo nginx -t. If it reports a syntax error, open the file named in the error, fix the block, then runsudo systemctl reload nginx. - The container exits immediately: run
docker compose logs --tail=100inside/opt/myapp/app. Missing environment values and bad image names are the usual causes. - Port 8000 is not listening: run
docker psandsudo ss -ltnp | grep 8000. If nothing is listening, the app never started or mapped a different internal port. - HTTPS certificate request fails: run
sudo certbot certificatesand confirm that DNS points to this server and port 80 is open. - App works locally but not from the browser: check
curl -I http://127.0.0.1:8000on the VPS, then inspect the firewall withsudo ufw statusorsudo firewall-cmd --list-all.
For rollback, redeploy the earlier Git commit and restart the stack.
cd /opt/myapp/app
git log --oneline -n 5
git checkout <previous-commit-hash>
docker compose up -d --force-recreate
sudo systemctl restart myapp-compose.serviceReplace <previous-commit-hash> with a real commit from git log --oneline. That gives you a deterministic fallback without rebuilding the whole server.
Keep the deployment maintainable
Once the first release is live, keep the workflow simple. Update the base system regularly, store secrets outside the image, and make every release point to a Git commit you can explain to a customer or teammate. That is the difference between a one-off container demo and a deployment you can support through migrations, incidents, and growth.
Hostperl customers who want a clean path from Git to production usually do best on a VPS with enough headroom for Docker, logs, and reverse proxy traffic. If you need a server for repeated launches or client handoffs, start with Hostperl VPS hosting and keep the workflow simple.
For teams that expect more than one service, build in time for a proper deployment check and health monitoring. Hostperl’s support team can help you choose the right size before you push the first container.
FAQ
Do I need Docker Compose v2 for this?
Yes. This tutorial uses docker compose, which is the current Compose plugin flow. It is easier to support and more predictable across restarts.
Should my app listen on 0.0.0.0 or 127.0.0.1?
Use 127.0.0.1 on the host mapping in this setup. Nginx should be the public entry point, not the app container.
Can I deploy from GitHub Actions or another CI system later?
Yes. Start with a manual Git pull and Compose restart, then automate only after you know the rollback path works.
What if I am migrating from a panel or older VM?
Keep the old server live until the new stack passes HTTP, HTTPS, login, and reboot checks. Hostperl’s migration-friendly VPS workflow makes that cutover much easier.
