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 operating-system kernel instead of running a separate guest kernel for every workload. Capacity still depends on measured CPU, memory, storage, I/O, and application demand; a container count or one VPS size is not a universal guarantee.
Step-by-step to run an Nginx container
- Open your terminal and run this command:
docker run -d -p 80:80 nginx
- 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 | Measured host baseline | Workload baseline plus redeploy headroom | Containers, builds, and caches compete for memory |
| Storage | Images, volumes, and logs | Growth plus backup headroom | Persistent data and logs can exhaust the filesystem |
| CPU | Measured steady and peak demand | Peak plus operational headroom | Builds and services create CPU bursts |
| OS | A Docker-supported Linux release | A supported release the operator can patch | Support and update ownership matter |
Critical requirements
- A provider environment compatible with current Docker Engine requirements
- Outbound internet on ports 80/443 for image pulls
- Root or sudo access for installation
Choose the initial allocation from a test deployment, then measure memory pressure, CPU saturation, disk growth, and I/O before production cutover.
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.
Connect with a named administrative account. Prefer a tested SSH-key login and keep a separate recovery path:
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:
uname -r
To verify your kernel version is 4.0 or higher:
[ $(uname -r | cut -d. -f1) -ge 4 ]; echo "Kernel OK"; echo "Kernel too old";
To verify your operating system version is Ubuntu 22.04 or newer, run:
cat /etc/os-release | grep VERSION
To confirm you have at least 2 GB of available memory, run:
free -h
To be sure you have at least 20 GB of free disk space on the root partition, run:
df -h /
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
- enough measured headroom for the intended containers and deployment process
If a required Docker dependency or resource check fails, resolve it before installing the runtime.
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:
Pre-change warning: Inventory existing packages, containers, images, volumes, Compose projects, daemon configuration, and firewall rules. Confirm backups or exports for persistent data and document rollback before removing packages, pruning resources, replacing configuration, or recreating services.
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:
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)
Follow Docker’s current official Ubuntu repository method instead of a convenience script.
First, set up Docker’s official APT repository. To add Docker’s GPG key, run:
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:
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME:-$VERSION_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Now install Docker and all required components:
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Start and enable the Docker service so it runs automatically:
sudo systemctl start docker
sudo systemctl enable docker
sudo systemctl status docker
You should see active (running) in green. If not, check the logs:
sudo journalctl -u docker --no-pager
To run Docker commands without sudo, add your user to the docker group:
The Docker group grants root-level control of the daemon. Add only trusted administrators; use Docker Rootless mode when the design requires a daemon without root privileges.
sudo usermod -aG docker $USER
Log out and log back in for group changes to take effect, or run:
newgrp docker
Finally, verify that everything is working:
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.
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:
docker run -d --name myapp nginx
To list running containers:
docker ps
To list all containers, including stopped ones:
docker ps -a
To stop a container:
docker stop myapp
To start it again:
docker start myapp
To remove a container:
docker rm myapp
Image management
To pull an image:
docker pull nginx:1.25
To list images:
docker images
To remove an image:
docker rmi nginx:1.25
To build an image from a Dockerfile:
docker build -t myapp .
Debugging and monitoring
To view container logs:
docker logs myapp
To follow logs in real time:
docker logs -f myapp
To execute commands inside a container:
docker exec -it myapp /bin/bash
To monitor resource usage:
docker stats
Cleanup (use carefully)
To remove stopped containers:
docker container prune
To remove unused images:
docker image prune
For a full cleanup of everything unused, review docker system df and the pre-change warning first. -a expands deletion beyond dangling 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:
docker compose version
Now create your first docker-compose.yml file with the following content:
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:
docker compose up -d
Check the status of all services:
docker compose ps
View logs in real time:
docker compose logs -f
Stop everything when you are done:
docker compose down
Verify volumes were created:
docker volume ls
With a single file describing your entire infrastructure, you can deploy real applications quickly and reproduce the setup anywhere.
Need a Docker VPS with room for real containers?
Host Docker workloads on a self-managed Virtarix VPS with root access, NVMe storage and one snapshot, plus customer-owned recovery copies and measured capacity beyond a first container test.
Step 6: security hardening (do not skip this)
Published container ports can bypass rules managed only through UFW. Decide which ports must be public, bind private services to loopback or an internal network, and enforce additional policy in Docker-compatible packet-filter chains such as DOCKER-USER. Keep a tested SSH session open while changing firewall rules:
sudo ufw --force enable
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
Only publish ports the workload needs. Do not disable Docker's iptables or ip6tables management as a generic firewall fix; Docker documents that doing so is likely to break container networking.
Next, create a non-root user inside your containers by adding this to your Dockerfile:
RUN adduser --disabled-password --gecos '' appuser
USER appuser
Keep Docker updated regularly. Check for updates monthly:
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:
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:
services:
web:
image: nginx
mem_limit: 512m
cpus: 0.5
restart: unless-stopped
Implement health checks so Docker can automatically restart unhealthy containers:
services:
web:
image: nginx
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", ""]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
Configure logging and log rotation to avoid disk bloat. Merge these keys with existing valid daemon configuration, validate the JSON, and restart during a maintenance window with a rollback copy:
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:
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:
sudo systemctl status docker
If it is not running, start it:
sudo systemctl start docker
Still having problems? Make sure your user is in the Docker group:
groups | grep docker
“docker: command not found”
This usually means the installation failed. Reinstall Docker properly:
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:
sudo netstat -tulpn | grep :80
Then kill the process or change the Docker port:
docker run -p 8080:80 nginx
Out of disk space
Check Docker disk usage:
docker system df
Clean up unused data only after reviewing the pre-change warning. --volumes can delete application data that no container currently references:
docker system prune -a --volumes
Image pull failures
Check internet connectivity:
ping docker.io
Try pulling from a different registry:
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:
## Overall system
htop
## Docker specific
docker stats --no-stream
## Disk usage
docker system df
Update containers:
## Pull latest images
docker compose pull
## Restart with new images
docker compose up -d
Clean up only after previewing the candidates and confirming the pre-change backup and rollback path; plain docker system prune removes stopped containers, unused networks, dangling images, and build cache:
## 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 an illustrative multi-service layout. It still needs workload-specific TLS, backups, secret handling, monitoring, update/redeploy, and rollback design before production use:
myapp/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── app/
│ └── Dockerfile
└── .env
Security tip: Never commit .env files to version control. Store secrets in an access-controlled mechanism or a mode-600 environment file outside the repository, and rotate values exposed in shell history, logs, or source control.
docker-compose.yml
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:
- DATABASE_URL=mysql://user:${DB_PASSWORD}@database:3306/myapp
depends_on:
- database
- redis
restart: unless-stopped
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
mysql_data:
redis_data:
Deploy it:
chmod 600 .env
docker compose up -d
docker compose ps
curl http://your_server_ip
Before updating it, back up and restore-test persistent volumes, record deployed image digests and the Compose file, and validate intended images in staging. Redeploy with docker compose up -d, inspect health and logs, and roll back with the previous definition, image digests, and application data if verification fails.
Conclusion
You now have a starting workflow for running Docker on a VPS. Select resources from measurements and retain headroom for builds, updates, recovery, and traffic peaks.
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 customer-operated control, but images, volumes, networking, and data services are portable only when migration and recovery are tested.
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 self-managed VPS sizes for Docker containers, Compose stacks, registries, and production services with root access, NVMe storage, IPv4 + IPv6 and one snapshot.
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