Ahosting Logo
Knowledge Base

Running Docker on a VPS

Two things about Docker that people do not expectPublished ports bypass your firewall· Docker writes rules ahead of yours· so a container port is reachable even when the firewall says noBind to localhost instead· publish to 127.0.0.1 and proxy in front· then the firewall rules mean what they sayThe second surpriseAccess to the Docker socket is effectively root on the host. Anything with that socket has themachine.

Docker packages an application with its dependencies so it runs the same anywhere. On a VPS that solves a real problem. Two applications needing conflicting versions of the same thing, and introduces two others that people do not expect.

What it genuinely helps with

Conflicting dependencies. One application needs an old runtime and another needs a current one. Containers let both run without either being wrong.

Reproducibility. The same image runs identically on your machine and the server, which removes an entire category of "it works locally".

Clean removal. Delete the container and nothing is left scattered across the filesystem.

The patching obligation does not go away

This is the misunderstanding worth correcting first.

A container image contains an operating system. An image built eighteen months ago carries eighteen months of unpatched vulnerabilities, and nothing tells you. The container runs perfectly.

Your system's automatic security updates do not reach inside images. Keeping containers patched means rebuilding them on a schedule and redeploying, which is work you have taken on rather than avoided.

Containers move where the patching happens. They do not remove it.

Docker bypasses your firewall

The one that surprises people, and it is a genuine exposure.

Publishing a port with -p 5432:5432 writes rules that sit ahead of the ones your firewall tool manages. A database you believed was closed to the internet is reachable, and the firewall's own status output shows it as blocked.

Bind to localhost explicitly when only the host needs access:

-p 127.0.0.1:5432:5432

And check what is actually listening from outside rather than trusting the firewall configuration. A database port open to the internet is scanned and attacked within hours. For the wider posture, see securing your VPS.

Data has to live outside the container

Containers are disposable by design. Anything written inside one is gone when it is replaced, and replacing containers is the normal way to update them.

Use a named volume or a bind mount for databases, uploads and anything else that must survive:

docker run -v /home/user/data:/var/lib/postgresql/data ...

Then back up the volume, not the container. A backup of a container image is a backup of the software, not of your data, and that distinction is discovered at the worst moment.

Compose, for anything with more than one part

An application plus a database is two containers that need to find each other. Managing that with individual commands works once and is not repeatable.

A compose file describes the whole arrangement (services, volumes, networks, environment) in one place that can be version-controlled. Recreating the stack becomes one command.

Keep secrets out of it. Environment variables in a committed compose file are credentials in your repository, and removing them later does not remove them from the history.

It costs memory

Each container carries its own userspace, and on a small VPS that adds up faster than people expect. Three containers each holding a runtime is three copies of a runtime.

Set memory limits per container so one cannot starve the others: without them, an application with a leak takes the whole machine down rather than just itself.

And watch disk. Old images, stopped containers and unused volumes accumulate silently until the filesystem fills, which stops every service at once:

docker system df
docker system prune

Be careful with prune. It removes unused volumes too, and "unused" includes a volume whose container you just removed.

When not to use it

A single PHP application on a single server does not need containers. Installing the runtime directly is simpler, uses less memory, and is patched by the system's own updates.

Containers earn their place when you have conflicting dependencies, several environments to keep identical, or a deployment process that benefits from immutable artefacts. Using them because they are modern adds work without solving a problem you had.

Reclaiming the disk Docker consumes

Images, stopped containers and unused volumes accumulate, and on a modest VPS they are a common reason the disk fills.

docker system df
docker system prune -a --volumes --dry-run 2>/dev/null || docker system df -v

The first command shows how much each category is using and how much is reclaimable, which is the number that matters. Old images from previous deployments are usually the bulk of it.

Prune deliberately rather than on a schedule: --volumes removes volumes no container references, and a volume holding a database that is temporarily stopped is exactly that. Read what the dry run proposes before running it for real.

The failure this prevents is the one that stops everything at once. Understanding inodes explains the related limit, since layered images produce very large numbers of small files.

Logs grow without limit by default

A container's output is captured and written to disk, and with the default driver nothing rotates it.

An application logging steadily fills the disk over weeks, and the file is not where anyone looks because it is under Docker's own directory rather than in the usual places.

{
 "log-driver": "json-file",
 "log-opts": { "max-size": "10m", "max-file": "3" }
}

Placed in the daemon configuration, that applies to new containers. Existing ones keep their original setting until recreated, which is why adding it does not shrink anything immediately. Managing logs and log rotation deals with finding the current sizes.

Restart policy, or nothing comes back

A container started by hand does not return after a reboot, which is the Docker version of a service that was never enabled.

docker run -d --restart unless-stopped ...
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' CONTAINER

unless-stopped is usually correct: it restarts after a crash or a reboot, and respects a container you stopped deliberately.

Verify by rebooting on purpose rather than assuming. A stack that has been running for months without a restart has never demonstrated that it comes back. Running an application as a systemd service walks through the same discipline outside Docker.

Know which layer to look at when something fails

A failure inside a container is diagnosed differently from one on the host, and the first step is establishing which you have.

docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker logs --tail 50 CONTAINER
docker exec -it CONTAINER sh

A container that keeps restarting shows it in the status column, and its logs hold the reason, frequently a missing environment variable or a dependency that was not ready.

A container running normally while the service is unreachable points at the port mapping or the host's firewall instead, which is a different investigation entirely.