keepcalculator

06 Numbers and Powers

Docker Best Practices for Running Containers on a VPS

Card No. 06·53123 · Updated · Standard formula, shown below

To run Docker containers on a VPS safely and efficiently, follow best practices around image selection, resource limits, security, and persistent storage. This guide covers the essential steps and common pitfalls, from choosing a base image to setting up automatic restarts and backups.

Why Use Docker on a VPS

A Virtual Private Server (VPS) gives you a dedicated slice of a physical server, with your own operating system and full root access. Docker adds a layer of isolation and reproducibility on top of that. Instead of installing software directly onto the host OS and hoping nothing conflicts, you package each application with its dependencies into a container. This makes deployment consistent across environments, simplifies scaling, and allows you to run multiple versions of the same software side-by-side without interference.

For a solo developer or small team, Docker on a VPS is often the fastest way to deploy web apps, databases, or background workers. It also makes rollbacks trivial: if a new version fails, you can restart the previous image in seconds. However, running containers on a single VPS is not the same as running them on a managed Kubernetes cluster. You are responsible for the host’s security, resource allocation, and data persistence.

Choosing a Base Image and Keeping It Small

The base image you choose determines the size, attack surface, and compatibility of your container. Always prefer official images from Docker Hub or your registry of choice, as they are more likely to be maintained and scanned for vulnerabilities. For most applications, an Alpine-based image is a good starting point because it is extremely small (around 5 MB) and includes a package manager. However, if your application relies on native libraries that are not available in Alpine’s musl environment, consider Debian-based images like debian:bookworm-slim.

  • Use multi-stage builds to compile binaries in a full-featured image, then copy only the runtime artifacts into a slim final image.
  • Pin image versions (e.g., node:20-alpine instead of node:latest) to avoid unexpected updates breaking your container.
  • Scan images with tools like docker scan or Trivy before deploying to catch known vulnerabilities.

Keeping images small reduces disk usage and pull times, but more importantly, it reduces the number of packages that could contain security flaws.

Setting Resource Limits and Restart Policies

Unrestricted containers can consume all of your VPS’s memory or CPU, causing the host to become unresponsive. Always define resource limits in your docker run command or in your docker-compose.yml file. For example, to limit a container to 512 MB of memory and 1.5 CPU cores:

docker run --memory=512m --cpus=1.5 my-app

In Docker Compose, the equivalent is:

services:
  app:
    image: my-app
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.5'

Restart policies are equally important. If your container crashes or the VPS reboots, you want it to come back automatically. Use --restart unless-stopped for most services, which restarts the container unless you explicitly stop it. For one-off jobs, use --restart on-failure with a maximum retry count.

Persistent Storage with Volumes and Bind Mounts

Containers are ephemeral by design—any data written to the container’s writable layer is lost when the container is removed. For databases, user uploads, or configuration files, you need persistent storage. Docker volumes are the recommended way because they are managed by Docker and can be backed up easily. Bind mounts, which map a host directory into the container, are also common for development or when you need direct access to files.

For example, to mount a host directory /var/www/html as /usr/share/nginx/html in an Nginx container:

docker run -v /var/www/html:/usr/share/nginx/html nginx

In Compose:

services:
  web:
    image: nginx
    volumes:
      - /var/www/html:/usr/share/nginx/html

Always ensure the host directory has the correct permissions—if the container runs as a non-root user, it must be able to read/write that directory. A common mistake is to set permissions on the volume after the container starts, which can cause permission denied errors.

Networking and Exposing Ports

By default, Docker containers are isolated from the host’s network. To make a service accessible from the internet, you must map a host port to a container port using -p or the ports key in Compose. For example, -p 8080:80 forwards host port 8080 to container port 80. Avoid exposing ports on 0.0.0.0 unless necessary; instead, bind to a specific interface like 127.0.0.1 for internal services (e.g., a database) so they are not reachable from the public internet.

For production, consider using a reverse proxy like Nginx or Traefik in a separate container to handle TLS termination and route traffic to your application containers. This way, you only expose ports 80 and 443 on the host, and all other services remain on an internal Docker network.

Security Hardening for Containers on a VPS

Running containers as root is a common security risk. If a container is compromised, the attacker may gain root access to the host. Always create a non-root user in your Dockerfile and use the USER directive. For example, in a Node.js image:

FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
USER app
CMD ["node", "server.js"]

Additionally, consider these practices:

  • Use read-only root filesystems (--read-only) to prevent writes to the container’s filesystem, and mount volumes for any writable directories.
  • Set cap-drop to remove unnecessary Linux capabilities, e.g., --cap-drop=ALL and then add back only what you need.
  • Keep the host’s Docker daemon and the VPS OS updated with security patches.
  • Use Docker’s content trust to verify image signatures if you are pulling from untrusted sources.

Monitoring and Logging

You cannot manage what you do not see. Docker provides built-in logging drivers, but for a single VPS, the default JSON-file logging is often sufficient. However, logs can grow indefinitely and fill your disk. Configure log rotation in the daemon or per-container:

docker run --log-opt max-size=10m --log-opt max-file=3 my-app

For monitoring, use docker stats to see live CPU and memory usage. For more advanced monitoring, consider installing cAdvisor or Prometheus, but that may be overkill for a single VPS. At minimum, set up a simple health check in your container and use a process manager like systemd to ensure the Docker daemon itself is running.

Backup and Recovery Strategies

Backing up containerized data is different from backing up a traditional server. You need to back up the volumes, not the containers themselves. For a database, use the database’s native dump tools (e.g., pg_dump for PostgreSQL) and store the dump on a separate volume or remote storage. For file storage, you can copy the volume directory using docker cp or a tool like restic.

Automate backups with a cron job on the host that runs a backup container or a script. Test your backups regularly by restoring them on a fresh container. Also, consider using Docker’s built-in docker commit to snapshot a container’s state, but that is not a substitute for proper volume backups.

Frequently Asked Questions

How do I update a running container on my VPS?

Pull the new image, then recreate the container with the same configuration. If using Docker Compose, run docker compose pull followed by docker compose up -d. To minimize downtime, use a rolling update strategy or a load balancer if you have multiple instances.

Should I use Docker Compose or plain docker run on a VPS?

Docker Compose is almost always better for anything beyond a single one-off container. It lets you define services, networks, volumes, and environment variables in a YAML file, making your setup reproducible and easier to manage. For a production VPS, you can still use Compose with docker compose up -d.

Can I run Docker on a cheap VPS with only 1 GB RAM?

Yes, but you need to be careful. The Docker daemon itself uses about 100 MB RAM. Choose lightweight images (Alpine), limit memory per container, and avoid running too many services. A typical web app with a small database can fit in 1 GB if you set limits and disable unused features.

How do I secure the Docker daemon socket?

The Docker socket (/var/run/docker.sock) gives full control over the host. Never expose it to containers unless absolutely necessary. If you must, use a read-only mount and consider using a socket proxy like docker-socket-proxy to restrict commands. Also, avoid running the Docker daemon on a public TCP port without TLS.

What is the difference between a volume and a bind mount?

A volume is managed by Docker and stored under /var/lib/docker/volumes. It is the recommended way for persistent data because it is portable and can be backed up with Docker commands. A bind mount maps a host directory directly into the container, which is useful for sharing configuration files or during development, but it is less isolated and can have permission issues.

How do I make my container start on boot?

Use the restart policy unless-stopped or always. Docker will automatically start the container when the daemon starts, which happens on boot if the Docker service is enabled. You can enable it with systemctl enable docker on most Linux distributions.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →