Launch a self-hosted Docker VPS in minutes
Self-hosted containers on a server you control. Order a Docker VPS, paste one install block from Docker's official repository, and your first docker compose up runs a few minutes later.
What you can do with a self-hosted Docker VPS
One server and as many containers as it fits. The app, the database, the queue, and the reverse proxy run side by side — each in its own container, with its own dependencies.
Deploy an entire application stack from one file
Pull ready images instead of building servers
Release updates without losing data
Run a microservices architecture on one server
Recover from reboots without manual intervention
Run Docker without root privileges
Choose a Docker VPS hosting plan
Docker Engine's own footprint is small, so size the plan for what runs inside the containers. We recommend starting with a Start VPS with 2 CPU cores and 2 GB of RAM. For a typical Compose stack with a database and reverse proxy, choose a Medium VPS with 4 GB of RAM.
- 40 CountriesFrom$10.19 / 1 month$9.68 with is*smart
- Xeon 2x2.20 GHz CPU
- 2 Gb RAM
- 30GB SSD Drive
- 3 Tb Bandwidth
- 40 CountriesFrom$21.24 / 1 month$20.18 with is*smart
- Xeon 3x2.20 GHz CPU
- 4 Gb RAM
- 40GB SSD Drive
- Unmetered Bandwidth
- 40 CountriesFrom$33.99 / 1 month$32.29 with is*smart
- Xeon 4x2.20 GHz CPU
- 8 Gb RAM
- 50GB SSD Drive
- Unmetered Bandwidth
Boost Your VPS Server Resources
Check out how you can change the configuration of your VPS. You can purchase additional drive space, increase CPU performance, add RAM, and take advantage of additional services without having to switch to a new plan.
How to deploy Docker on a VPS
Go from a clean Ubuntu VPS to a running Docker stack behind HTTPS in five steps.
Step 01 — Order your VPS and connect
Choose an Ubuntu VPS in a location close to your users or the external APIs your containers call, then connect over SSH. The commands below run in a root shell.
ssh root@your-server-ipIf direct root login is disabled, connect with your regular account and run sudo -i.
Step 02 — Install Docker Engine and Compose
Install Docker Engine and the Compose plugin from Docker's official apt repository:
apt updateapt install ca-certificates curlinstall -m 0755 -d /etc/apt/keyringscurl -fsSL https://download.docker.com/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.ascchmod a+r /etc/apt/keyrings/docker.asc tee /etc/apt/sources.list.d/docker.sources <<EOFTypes: debURIs: https://download.docker.com/linux/ubuntuSuites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")Components: stableArchitectures: $(dpkg --print-architecture)Signed-By: /etc/apt/keyrings/docker.ascEOF apt updateapt install docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-pluginsystemctl enable --now dockerdocker run --rm hello-worldStep 03 — Point your domain at the VPS
Create an A record pointing your application domain to the Docker VPS. Caddy needs the domain to resolve correctly before it can obtain an HTTPS certificate.
Type: AName: appValue: your-server-ipAllow inbound TCP traffic on ports 80 and 443 in the server firewall and your provider's control panel.
Step 04 — Define the Docker stack
Create a project directory:
mkdir -p /opt/docker-appcd /opt/docker-appCreate .env and replace the example value with a long, URL-safe password:
POSTGRES_PASSWORD=replace-with-a-long-random-stringCreate Caddyfile:
app.yourdomain.com { reverse_proxy app:8080}Create compose.yaml. Replace your-org/your-app:1.4.2 with the image and version used by your application. The older docker-compose.yml filename is still supported.
services: app: image: your-org/your-app:1.4.2 restart: unless-stopped environment: DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app depends_on: db: condition: service_healthy db: image: postgres:16 restart: unless-stopped environment: POSTGRES_DB: app POSTGRES_USER: app POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - db-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s timeout: 5s retries: 10 caddy: image: caddy:2 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config volumes: db-data: caddy-data: caddy-config:Only Caddy publishes ports on the host. The application and database remain available only inside the private Compose network.
Step 05 — Start the stack
Protect the environment file, start the containers, and check their status:
chmod 600 .envdocker compose up -ddocker compose psCaddy obtains and renews the HTTPS certificate automatically and redirects HTTP requests to HTTPS. The named volumes keep the database data and Caddy certificates when containers are recreated.
Common Issues
- The app starts before the database is ready — plain depends_on guarantees start order, not readiness. Give the database a health check and depend on it with condition: service_healthy, as above.
- Published ports ignore ufw — Docker routes container traffic in the nat table, before the chains ufw uses, so a published port answers even when ufw says it's closed. Publish only the proxy; binding to 127.0.0.1 also publishes the port, it just limits it to the loopback interface.
- Pulls start failing mid-deploy — Docker Hub allows 100 pulls per 6 hours per IPv4 address or IPv6 /64 subnet without authentication, and 200 per 6 hours on a free Personal account. Run docker login on the server before a deploy that pulls a lot.
- Data vanished after the container was recreated — data stored in a container's writable layer disappears when that container is removed. Keep persistent data in a named volume or bind mount.