Docker solves a problem every developer faces: “It works on my machine, but not in production.”
It packages your application with everything it needs into a lightweight container. Think of it as a shipping container for your code, running the same way whether it is on your laptop, a staging server, or a production VPS.
Containers share the host’s operating system kernel instead of spinning up entire virtual machines. As a result, you can run five to ten applications on a 4 GB VPS where two traditional VMs would struggle.
Step-by-step to run an Nginx container
- Open your terminal and run this command:
“`bash title="Run Nginx in a detached container on port 80" docker run -d -p 80:80 nginx
2. What happens next:
- Docker pulls the official Nginx image from Docker Hub.
- It creates a container in detached mode (`-d`).
- It maps port 80 of the container to port 80 of your VPS so you can visit the site in a browser.
This single command downloads Nginx, creates a container, and serves your website. No complex installation, no configuration conflicts, no headaches.
## Prerequisites
Skip the bare minimums that barely work and start with specifications that keep your environment stable.
| Component | Minimum | Recommended | Why this matters |
| --------- | ------------- | ---------------- | -------------------------------------------------------- |
| RAM | 2 GB | 4 GB+ | Docker uses about 300 MB; each container needs 50–500 MB |
| Storage | 20 GB SSD | 40 GB+ NVMe | Images pile up fast, logs grow, you need room to breathe |
| CPU | 2 vCPU | 4+ vCPU | Container startup and build processes are CPU-intensive |
| OS | Ubuntu 24.04+ | Ubuntu 24.04 LTS | Latest Docker features and best security patches |
### Critical requirements
- KVM or VMware virtualization (avoid OpenVZ)
- Outbound internet on ports 80/443 for image pulls
- Root or sudo access for installation
That 1 GB VPS might seem tempting, but you will hit memory limits the moment you try to build anything substantial. Start with 4 GB, your future self will thank you.
## Step 1: connect and verify your VPS
Alright, let’s get you connected to your server. I’ll assume your VPS provider gave you SSH credentials.
If you are still using password authentication in 2025, we can fix that later, but let’s start with the basics. First, connect to your VPS by running:
```bash title="Connect to the VPS over SSH"
ssh username@your_server_ip
Replace username and your_server_ip with the actual credentials your provider gave you. If you are on Windows and somehow do not have SSH built in, you can use a tool like PuTTY.
Now we need to make sure your system is ready for Docker, and to check if your kernel version is at least 4.0 (5.15 or newer is ideal), run:
“`bash title="Show the running Linux kernel version" uname -r
To verify your kernel version is 4.0 or higher:
```bash title="Check whether the kernel major version is at least 4"
if [ $(uname -r | cut -d. -f1) -ge 4 ]; then echo "Kernel OK"; else echo "Kernel too old"; fi
To verify your operating system version is Ubuntu 22.04 or newer, run:
“`bash title="Show operating-system version fields" cat /etc/os-release | grep VERSION
To confirm you have at least 2 GB of available memory, run:
```bash title="Show a human-readable memory summary"
free -h
To be sure you have at least 20 GB of free disk space on the root partition, run:
“`bash title="Show free space on the root filesystem" df -h /
Finally, to check that your VPS uses KVM or VMware virtualization (not OpenVZ), run:
```bash title="Show the virtual system product name"
sudo dmidecode -s system-product-name
You should see:
- Kernel version 4.0 or higher (5.15+ is best)
- Ubuntu 22.04 or newer
- At least 2 GB of RAM available
- 20 GB or more of free disk space
- Virtualization type showing KVM or VMware
If any of these checks fail, stop here and contact your VPS provider before moving on.
Step 2: clean installation (skip the headaches)
Here is something we wish someone had told us years ago: always clean out any old Docker remnants before installing. Leftover packages can create strange conflicts later.
First, remove any existing Docker-related packages by running:
“`bash title="Remove legacy Docker packages and unused dependencies" sudo apt-get remove docker docker-engine docker.io containerd runc -y sudo apt autoremove -y
Do not worry if you see messages like “package not found.” That is actually what you want—it means there is nothing old to interfere.
Next, update your system so you have the latest security patches and kernel updates before installing Docker:
```bash title="Update all Ubuntu packages"
sudo apt update && sudo apt upgrade -y
This step might take a few minutes, but it is essential to avoid problems later.
Step 3: install Docker (the right way)
For a production server in 2025, it is best to follow Docker’s official installation method instead of using the quick convenience script that is meant only for development.
First, set up Docker’s official APT repository. To add Docker’s GPG key, run:
“`bash title="Install Docker repository prerequisites and signing key" sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc
Next, add the repository to your APT sources:
```bash title="Attempt to add Docker's Ubuntu apt repository"
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc]
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" |
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Now install Docker and all required components:
“`bash title="Install Docker Engine and Compose plugins" sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Start and enable the Docker service so it runs automatically:
```bash title="Start enable and inspect the Docker service"
sudo systemctl start docker
sudo systemctl enable docker
sudo systemctl status docker
You should see active (running) in green. If not, check the logs:
“`bash title="Show Docker service journal entries" sudo journalctl -u docker –no-pager
To run Docker commands without sudo, add your user to the docker group:
```bash title="Add the current user to the docker group"
sudo usermod -aG docker $USER
Log out and log back in for group changes to take effect, or run:
“`bash title="Start a shell with the docker group active" newgrp docker
Finally, verify that everything is working:
```bash title="Show Docker's version and run the hello-world image"
docker --version
docker run hello-world
If you see Hello from Docker! you are all set. If not, we will troubleshoot in the next section.
Secure your firewall rules
Make your Docker firewall rules persistent so they survive reboots:
“`bash title="Attempt to persist the current iptables rules"
Make iptables rules persistent
sudo apt install iptables-persistent sudo iptables-save > /etc/iptables/rules.v4
## Step 4: essential Docker commands (your daily toolkit)
These are the commands we use every single day. Master them and you will feel at home with Docker.
### Container management
To run a container:
```bash title="Run a detached Nginx container named myapp"
docker run -d --name myapp nginx
To list running containers:
“`bash title="List running Docker containers" docker ps
To list all containers, including stopped ones:
```bash title="List all Docker containers"
docker ps -a
To stop a container:
“`bash title="Stop the myapp container" docker stop myapp
To start it again:
```bash title="Start the myapp container"
docker start myapp
To remove a container:
“`bash title="Remove the myapp container" docker rm myapp
### Image management
To pull an image:
```bash title="Pull the Nginx 1.25 image"
docker pull nginx:1.25
To list images:
“`bash title="List local Docker images" docker images
To remove an image:
```bash title="Remove the local Nginx 1.25 image"
docker rmi nginx:1.25
To build an image from a Dockerfile:
“`bash title="Build the myapp image from the current directory" docker build -t myapp .
### Debugging and monitoring
To view container logs:
```bash title="Show logs from the myapp container"
docker logs myapp
To follow logs in real time:
“`bash title="Follow logs from the myapp container" docker logs -f myapp
To execute commands inside a container:
```bash title="Open an interactive Bash shell in myapp"
docker exec -it myapp /bin/bash
To monitor resource usage:
“`bash title="Monitor live Docker container resource usage" docker stats
### Cleanup (use carefully)
To remove stopped containers:
```bash title="Prune stopped Docker containers"
docker container prune
To remove unused images:
“`bash title="Prune dangling Docker images" docker image prune
For a full cleanup of everything unused:
> **Safety check:** Confirm the target and keep a recent backup or snapshot. Preview the affected resources where possible, and document a tested recovery or rollback path before running this command.
```bash title="Prune all unused Docker resources and images"
docker system prune -a
Pro tip: Always give your containers descriptive names, such as --name web-frontend instead of leaving them with random IDs. Your future self, especially at 2 AM, will thank you.
Step 5: Docker Compose (where the magic happens)
This is where Docker becomes truly powerful. Instead of managing individual containers, you can define your entire application stack in a single file.
Docker Compose is already installed from the previous step. To verify it works, run:
“`bash title="Show the Docker Compose version" docker compose version
Now create your first `docker-compose.yml` file with the following content:
```yaml title="Define an Nginx and MySQL Compose stack"
version: '3.8'
services:
web:
image: nginx:1.25
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
restart: unless-stopped
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: your_secure_password
MYSQL_DATABASE: myapp
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
To launch your stack, run:
“`bash title="Start the current Docker Compose stack" docker compose up -d
Check the status of all services:
```bash title="Show Docker Compose service status"
docker compose ps
View logs in real time:
“`bash title="Follow Docker Compose logs" docker compose logs -f
Stop everything when you are done:
```bash title="Stop and remove the current Compose stack"
docker compose down
Verify volumes were created:
“`bash title="List Docker volumes created by Compose" docker volume ls
With a single file describing your entire infrastructure, you can deploy real applications quickly and reproduce the setup anywhere.
## Step 6: security hardening (do not skip this)
I have seen too many compromised servers because people skipped security. Let’s lock yours down. A critical warning before we start: when you expose container ports using Docker, those ports bypass your firewall rules completely. Many people miss this, and it is a serious security risk. Begin by configuring a UFW firewall with Docker in mind:
```bash title="Enable UFW and allow SSH HTTP and HTTPS"
sudo ufw --force enable
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
Only open the ports you actually need. Configure Docker to work with UFW:
“`bash title="Disable Docker-managed iptables rules" sudo systemctl stop docker echo '{ "iptables": false }' | sudo tee /etc/docker/daemon.json sudo systemctl start docker
Next, create a non-root user inside your containers by adding this to your `Dockerfile`:
```dockerfile title="Create and use a non-root container user"
RUN adduser --disabled-password --gecos '' appuser
USER appuser
Keep Docker updated regularly. Check for updates monthly:
“`bash title="Review and install Docker package upgrades" sudo apt update && sudo apt list –upgradable | grep docker sudo apt upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Scan images for vulnerabilities with Docker Scout:
```bash title="Scan the latest Nginx image with Docker Scout"
docker scout quickview nginx:latest
Remember: containers inherit the host’s security. If your VPS is compromised, your containers are too, so do not skip these steps.
Step 7: production-ready practices
These are the steps that separate hobby projects from true production deployments. Always set resource limits in your docker-compose.yml to prevent a single container from consuming all resources:
“`yaml title="Attempt to limit Nginx memory and CPU in Compose" services: web: image: nginx deploy: resources: limits: memory: 512M cpus: '0.5' restart: unless-stopped
Implement health checks so Docker can automatically restart unhealthy containers:
```yaml title="Attempt to add an Nginx Compose health check"
services:
web:
image: nginx
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", ""]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
Configure proper logging and log rotation to avoid disk bloat. Edit the Docker daemon configuration:
“`bash title="Configure Docker JSON-file log rotation" sudo mkdir -p /etc/docker echo '{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker
Set up monitoring to keep track of performance and issues. For example, you can use Prometheus:
```yaml title="Attempt to add Prometheus monitoring to Compose"
services:
monitoring:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: unless-stopped
These steps are not optional—they are essential for any application you intend to keep online and stable.
Troubleshooting common Docker issues
Here are the problems seen most often and how to fix them.
“Cannot connect to Docker daemon”
Check if Docker is running:
“`bash title="Show the Docker service status" sudo systemctl status docker
If it is not running, start it:
```bash title="Start the Docker service"
sudo systemctl start docker
Still having problems? Make sure your user is in the Docker group:
“`bash title="Check whether the current user has docker group access" groups | grep docker
### “docker: command not found”
This usually means the installation failed. Reinstall Docker properly:
```bash title="Reinstall Docker Engine and Compose plugins"
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Port already in use
Find what is using the port:
“`bash title="Find the process listening on TCP port 80" sudo netstat -tulpn | grep :80
Then kill the process or change the Docker port:
```bash title="Run Nginx on host port 8080"
docker run -p 8080:80 nginx
Out of disk space
Check Docker disk usage:
“`bash title="Show Docker disk usage summary" docker system df
Clean up unused data:
> **Safety check:** Confirm the target and keep a recent backup or snapshot. Preview the affected resources where possible, and document a tested recovery or rollback path before running this command.
```bash title="Prune unused Docker resources and volumes"
docker system prune -a --volumes
Image pull failures
Check internet connectivity:
“`bash title="Ping docker.io to test connectivity" ping docker.io
Try pulling from a different registry:
```bash title="Pull an Nginx image from Quay"
docker pull quay.io/nginx/nginx
We keep these commands handy because even after years of using Docker, unexpected issues can still pop up.
Monitoring and maintenance
Here is a weekly maintenance routine that keeps Docker environments stable and efficient.
Check resource usage:
“`bash title="Review host and Docker resource usage"
Overall system
htop
Docker specific
docker stats –no-stream
Disk usage
docker system df
Update containers:
```bash title="Pull new Compose images and recreate services"
## Pull latest images
docker compose pull
## Restart with new images
docker compose up -d
Clean up weekly:
Safety check: Confirm the target and keep a recent backup or snapshot. Preview the affected resources where possible, and document a tested recovery or rollback path before running this command.
“`bash title="Prune Docker resources and inspect container log sizes"
Remove old containers and images
docker system prune
Check logs are not filling disk
du -sh /var/lib/docker/containers/*/
Monitor key metrics to catch issues early:
- Memory usage should remain below 80%.
- Keep at least 5 GB of free disk space.
- Ensure no containers are constantly restarting.
- Confirm logs are not growing uncontrollably.
Setting simple alerts for these metrics helps prevent outages before they start.
## Real-world deployment example
Here is a production-ready setup that can be adapted to most web applications. Directory structure:
```text title="Review an example Docker application directory tree"
myapp/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── app/
│ └── Dockerfile
└── .env
Security tip: Never commit .env files to version control. They often contain passwords and API keys.
docker-compose.yml
“`yaml title="Define a web app database and Redis Compose stack" services: reverse-proxy: image: nginx:1.25-alpine ports:
- "80:80"
- "443:443"
volumes:
- ./nginx:/etc/nginx/conf.d
- ./ssl:/etc/ssl/certs
depends_on:
- app
restart: unless-stopped app: build: ./app environment:
- DATABASEURL=mysql://user:${DBPASSWORD}@database:3306/myapp
depends_on:
- database
- redis
restart: unless-stopped database: image: mysql:8.0 environment: MYSQLROOTPASSWORD: ${DBROOTPASSWORD} MYSQLDATABASE: myapp MYSQLUSER: user MYSQLPASSWORD: ${DBPASSWORD} volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped redis: image: redis:7-alpine volumes:
- redis_data:/data
restart: unless-stopped volumes: mysqldata: redisdata:
Deploy it:
```bash title="Create Compose passwords and deploy the example stack"
## Create .env file with your passwords
echo "DB_PASSWORD=your_secure_db_password" > .env
echo "DB_ROOT_PASSWORD=your_root_password" >> .env
## Launch everything
docker compose up -d
## Verify operation
docker compose ps
curl http://your_server_ip
This structure works for most web applications and can be customized for any tech stack.
Conclusion
You now have everything required to run Docker effectively on a VPS. Starting with at least 4 GB of RAM ensures containers have the resources they need for real workloads.
Use Docker Compose for any project that involves multiple containers. It maintains consistent deployments and simplifies scaling or migration.
Implement monitoring, resource limits, and strong security practices from the beginning. Pay particular attention to firewall settings, as exposed ports can bypass standard rules.
Running Docker on a VPS provides an ideal balance of control, performance, and cost. It avoids vendor lock-in and keeps your environment fully portable.
Begin with a small project, such as containerizing an existing application or deploying a new service. Once you experience the simplicity of containerized deployments, you will see why Docker is a core tool for modern infrastructure.
Ready to host Docker on Virtarix VPS?
Compare VPS sizes for Docker containers, Compose stacks, registries, and production services with root access, NVMe storage, IPv4 + IPv6, snapshots, and backups.
VPS S
For small sites, dev servers and Docker
- ✓ 3 cores
- ✓ 6 GB
- ✓ 50 GB NVMe
- ✓ Unlimited
VPS M
For growing apps, websites and staging
- ✓ 6 cores
- ✓ 16 GB
- ✓ 100 GB NVMe
- ✓ Unlimited