To install Docker on a VPS running Ubuntu 24.04, add Docker’s official apt repository, install docker-ce with the Compose plugin, and confirm it works with sudo docker run hello-world. On a server with a public IP, three more steps matter just as much: cap container logs, stop published ports from bypassing UFW, and decide who gets root-equivalent docker access.
The install, logging and firewall commands below come from Docker’s documentation as checked on October 3, 2026, when the current release was Docker Engine 29.8.2 (released September 30, 2026). You finish with a small PostgreSQL stack that only you can reach, through an SSH tunnel.
Key takeaways
- Install Docker from Docker's own apt repository (docker-ce plus docker-compose-plugin); the current docs write a deb822 docker.sources file instead of the older docker.list one-liner.
- Ports you publish with -p or a Compose ports: entry bypass UFW, so bind admin and database ports to 127.0.0.1 or do not publish them, and test from outside.
- The default json-file log driver never rotates; set the local driver or max-size and max-file in /etc/docker/daemon.json, then re-create existing containers.
- Membership in the docker group is root-equivalent on the host, so grant it only to people you would give sudo.
- Ubuntu's unattended-upgrades ignores third-party repositories by default, so plan Docker Engine upgrades yourself.
Before you start: requirements and the right install method
You need a 64-bit Ubuntu server and a non-root user with sudo. Docker supports Ubuntu 26.04, 24.04 and 22.04 LTS on x86_64, arm64 and a few other architectures; this guide uses 24.04 LTS. If you have not logged in yet, start with connecting to your VPS over SSH, then work through the new Linux VPS security checklist: SSH keys, no root login, updates, and UFW with SSH allowed before you enable it.
Docker needs a VPS that runs its own Linux kernel, which is what KVM virtualization gives you. Every HourlyVPS KVM server boots its own kernel, so the steps below apply unchanged.
| Install method | What you get | Use it on a VPS? |
|---|---|---|
Docker’s apt repository (docker-ce) | Current Docker Engine, Compose and Buildx plugins, updates through apt | Yes. This guide uses it. |
Ubuntu’s docker. package | Docker packaged by Ubuntu; Docker’s docs list it as an unofficial package that conflicts with theirs | Only if you prefer Ubuntu’s packaging. Never mix it with docker-ce. |
Convenience script (get.) | The same packages, set up non-interactively | Docker says it “isn’t recommended for production environments.” Fine for a throwaway test box. |
Manual .deb files | Pinned versions without a repository | Only if you cannot use the repository. You must download every upgrade yourself. |
| Docker Desktop | A desktop app that runs Docker inside its own VM | No. A headless server needs Docker Engine only. |
How to install Docker on a VPS running Ubuntu 24.04
Run these four steps as your sudo user, in order. Each command is copied from Docker’s Ubuntu install page.
Step 1: Remove conflicting packages
Ubuntu’s archive carries its own Docker-related packages (docker.io, docker-compose-v2, podman-docker and a few more) that conflict with Docker’s. This command removes whichever of them are installed. On a fresh image, apt usually reports that there is nothing to remove.
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)
Step 2: Add Docker’s apt repository
This downloads Docker’s signing key into /etc/apt/keyrings and writes the repository as a deb822 docker.sources file, the format Docker’s docs use today. Many older guides write a one-line docker.list file instead. Keep only one of the two, or apt will complain (see troubleshooting).
# Add Docker's official GPG key:
sudo apt update
sudo apt 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
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
The Suites line reads your release codename from /etc/os-release (noble on 24.04), so the same block also works on 22.04 and 26.04. The Architectures line does the same for amd64 or arm64.
Note: On Debian 12 or 13, follow Docker’s Debian page instead. Both URLs end in /debian instead of /ubuntu, and Suites uses $VERSION_CODENAME. Everything after this step is the same.
Step 3: Install Docker Engine and the Compose plugin
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker-ce: the Docker daemon (dockerd).docker-ce-cli: thedockercommand.containerd.io: the container runtime, bundled withrunc.docker-buildx-plugin: the BuildKit builder behinddocker build.docker-compose-plugin: thedocker composecommand.
Step 4: Verify the installation
sudo systemctl status docker
sudo docker run hello-world
The status should read active (running); press q to leave it. hello-world downloads a test image, prints a confirmation message and exits. On Ubuntu the Docker service starts at boot by default, so you do not need systemctl enable.
Is Docker Compose installed? (v2, v5 and docker-compose)
docker compose version
The plugin is called as docker compose, with a space. Docker’s Compose history page describes Compose v5 (2025) as functionally identical to v2, so a v5 version number is expected. The hyphenated docker-compose is Compose v1, a Python tool; Docker lists only v2 and v5 as supported. Compose ignores the old top-level version: key, so new files can leave it out.
Should you add your user to the docker group?
After the install, the docker group exists but has no members, so every command needs sudo. Adding your user to the docker group removes that step, and Docker’s docs are direct about what it costs:
“The docker group grants root-level privileges to the user.”
Docker Docs, Linux post-installation steps for Docker Engine
A member can start a container that mounts the host’s / and change any file on it. That is usually fine for the one admin of a VPS. Do not add service accounts, CI users or anyone you would not give sudo.
sudo usermod -aG docker $USER
Log out and back in so the new group applies, or run newgrp docker for the current shell. Then test without sudo:
docker run hello-world
If you used sudo docker before joining the group, you may see WARNING: Error loading config file: /home/user/.docker/config.json - stat /home/user/.docker/config.json: permission denied. Docker’s documented fix:
sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
sudo chmod g+rwx "$HOME/.docker" -R
The alternatives: keep typing sudo, which leaves an audit trail, or use rootless mode, which runs the daemon and containers as your user inside a user namespace.
Warning: The same root-level access applies to any container that mounts /var/run/docker.sock. Dashboards, auto-updaters and some reverse proxies ask for it in their Compose files. Mounting it gives that container control of the host, so only do it for software you trust as much as root.
Cap container logs before they fill the disk
Docker’s default logging driver, json-file, does not rotate logs: its max-size defaults to -1, which means no size limit. One chatty container can fill a VPS disk and take the database next to it down too. Fix it before you run real workloads.
For setups that are not Kubernetes nodes, Docker’s logging docs recommend the local driver. It rotates by default (five 20 MB files per container, compressed) and uses a more efficient format. Set it as the default, with a tighter cap, in /etc/docker/daemon.json:
sudo tee /etc/docker/daemon.json <<'EOF'
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
If the file already exists, merge these keys into it instead of overwriting it. Values under log-opts must be strings, hence "3" in quotes. Validate, restart and check:
sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
docker info --format '{{.LoggingDriver}}'
You should see configuration OK, and then local.
Warning: Only containers created after the change use the new settings. Existing containers keep their old log options until you re-create them: run docker compose up -d --force-recreate in each Compose project. Restarting the daemon also stops running containers. Those with a restart policy start again on their own.
Keep json-file only if a log shipper reads Docker’s JSON files directly, and give it the same max-size and max-file options; max-file only works when max-size is set.
Does Docker bypass UFW? Yes, for published ports
Yes. When you publish a port with -p 8080:80 or a Compose ports: entry, Docker adds NAT rules that redirect the traffic before it reaches the INPUT chain, which is where UFW’s rules live. So sudo ufw deny 8080 does not stop IPv4 traffic to a container published on port 8080.
“Packets are routed before the firewall rules can be applied, effectively ignoring your firewall configuration.”
Docker Docs, Packet filtering and firewalls: Docker and ufw
UFW still protects services that run directly on the host, such as sshd. What it cannot see is traffic to published container ports. A mapping without a host address listens on every address, IPv4 and IPv6 (0.0.0.0 and [::]), and Docker’s Compose reference warns that this can expose the container directly to the internet. Every HourlyVPS server has a public IPv4 address and IPv6, so check both.
Check what is exposed right now
docker ps --format 'table {{.Names}}\t{{.Ports}}'
An entry such as 0.0.0.0:8080->8080/tcp is open to the internet over IPv4, whatever UFW says. A second entry starting with [::]: means the port also listens on IPv6. 127.0.0.1:8080->8080/tcp is reachable from the server only. To confirm, test from your own computer, not from the server, using your server’s addresses in place of the examples:
curl -4 -m 5 http://203.0.113.10:8080
curl -6 -m 5 "http://[2001:db8::10]:8080"
Skip the second line if your own connection has no IPv6. A timeout or Connection refused means the port is closed from outside. Any HTTP response means it is open.
Which fix should you use?
| Situation | Fix | Where |
|---|---|---|
| Only other containers need the service (databases, caches) | Do not publish it. Containers on the same Compose network reach it by service name, for example db:. | compose.: no ports: entry |
| A reverse proxy on the same host, or you via an SSH tunnel | Publish to loopback: "127. | compose. or docker run -p |
| You want a safety net for every future container | Make 127. the default host address | /etc/ |
| A published port must accept only certain source IPs | Add a filter rule to the DOCKER-USER chain | iptables |
Turning Docker’s firewall rules off ("iptables": false) | Avoid it. Docker’s docs say it is not appropriate for most users and is likely to break container networking. | Not recommended |
Bind ports to 127.0.0.1
Put the host address in front of the mapping. In Compose, quote it:
ports:
- "127.0.0.1:8080:8080"
With docker run, Docker’s port-publishing page shows the IPv4 and IPv6 loopback forms together:
docker run -p 127.0.0.1:8080:80 -p '[::1]:8080:80' nginx
Docker releases older than 28.0.0 let other hosts on the same layer-2 network reach ports published to localhost. A repository install gives you 29.x; check yours with docker version.
Make localhost the default for every new network
Two daemon options change where a port mapping without a host address lands. "ip" covers the default bridge that docker run uses. "default-network-opts" covers new user-defined bridge networks, which is what Compose creates for each project. Here is the complete daemon.json with the log settings from above:
sudo tee /etc/docker/daemon.json <<'EOF'
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"ip": "127.0.0.1",
"default-network-opts": {
"bridge": {
"com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"
}
}
}
EOF
sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
Per the dockerd reference, networks that already exist keep their old options. Re-create a project’s network with docker compose down and then docker compose up -d; named volumes are kept. For the example stack below, docker network inspect pgdemo_default --format '{{json .Options}}' should then show host_binding_ipv4 as 127.0.0.1.
The trade-off: a port you want public now needs an explicit address, such as "0.0.0.0:443:443", and an explicit IPv4 address publishes on IPv4 only. The Caddy service in our n8n stack publishes "80:80" and "443:443" without an address, so with this default you would change those lines to "0.0.0.0:80:80", "0.0.0.0:443:443" and "0.0.0.0:443:443/udp". Test from outside after every change.
Allow only your IP with the DOCKER-USER chain
Docker processes the DOCKER-USER chain before its own forwarding rules, and it is the documented place for your own filters. First find the public interface name: it is the word after dev in this output, for example eth0.
ip route show default
These rules drop forwarded traffic from every source except 198.51.100.7 (use your own IP and interface name) and still accept replies to allowed connections. -I inserts at the top, so run them in this order: Docker’s docs require the accept rule above the drop rule.
sudo iptables -I DOCKER-USER -i eth0 ! -s 198.51.100.7 -j DROP
sudo iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -L DOCKER-USER -n --line-numbers
- The drop rule covers every published port. If a container also serves the public, such as a reverse proxy on 443, match the original destination port with
-m conntrack --ctorigdstportinstead, as Docker’s iptables page shows (it warns this can cost some performance). - These are IPv4 rules. If your Docker networks have IPv6 enabled, add matching
ip6tablesrules; Docker managesip6tablesby default. - Rules added by hand disappear at reboot. Re-apply them after Docker starts, for example from a systemd unit ordered after
docker.service, and check the chain after the next reboot. - Docker works with
iptables-nftandiptables-legacy. It does not support rulesets written directly withnfton a Docker host.
Community scripts such as ufw-docker wire UFW into DOCKER-USER so you can use ufw route rules. They are not part of Docker’s documentation, so test from outside before relying on one.
Do not open Docker’s own ports in UFW
A single Docker host needs no UFW rule for Docker itself: the daemon listens on a local Unix socket by default. Some tutorials still tell you to allow these ports. Leave them closed:
- 2375/tcp and 2376/tcp: the Docker API over the network, without and with TLS. Docker’s remote access page warns that an unsecured connection can let remote non-root users gain root access on the host.
- 2377/tcp, 7946/tcp+udp and 4789/udp: Swarm traffic between cluster nodes. Docker says 4789 should only be opened to a trusted network, never at a perimeter firewall.
To run docker commands from your own computer, use SSH instead, as Docker’s daemon-socket protection page shows. Run this locally; the server user must be in the docker group:
docker context create --docker host=ssh://[email protected] my-vps
docker context use my-vps
Your first Compose stack: PostgreSQL and Adminer on localhost
The official postgres page on Docker Hub ships a Compose example with Adminer, a web UI for databases, published as 8080:8080. On a laptop that is harmless. On a VPS it puts a database login page on the public internet, past UFW. Here is the same stack adapted for a server.
Create a project directory. Compose uses the directory name, pgdemo, as the project name:
mkdir -p ~/stacks/pgdemo
cd ~/stacks/pgdemo
Generate a random database password into a .env file that only you can read. Compose reads .env from the project directory automatically:
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env
Create compose.yaml (for example with nano compose.yaml) with this content:
services:
db:
image: postgres:18
restart: unless-stopped
shm_size: 128mb
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 10s
retries: 5
start_period: 30s
adminer:
image: adminer:6
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8080:8080"
volumes:
pgdata:
- No
ports:ondb. PostgreSQL is reachable only on the project network, asdb:5432. - Adminer is bound to
127.0.0.1. It is reachable from the server only, so UFW’s blind spot does not matter. - Pinned major tags (
postgres:18,adminer:6). A pull brings updates within that major version, never a surprise major upgrade. - The volume mounts at
/var/lib/postgresql. The image docs changed the data layout in PostgreSQL 18; versions 17 and older mount/var/lib/postgresql/datainstead. $${…}escapes the variable, so the shell inside the container expands it, not Compose. The healthcheck follows Docker’s startup-order example.restart: unless-stoppedbrings both containers back after a reboot or a daemon restart.
Start the stack and watch the database become healthy:
docker compose up -d
docker compose ps
docker compose logs -f db
Press Ctrl+C to stop following the logs; the containers keep running. To open Adminer, create an SSH tunnel from your own computer, using your server’s user and IP:
ssh -N -L 8080:127.0.0.1:8080 [email protected]
Leave that terminal open and browse to http://localhost:8080. Log in with System PostgreSQL, Server db, Username app, Database app, and the password from cat ~/stacks/pgdemo/.env on the server.
When you are done, docker compose down removes the containers and the network but keeps the pgdata volume. docker compose down -v deletes the volume too, and with it every row in the database.
Tip: To put a real web app online, keep the same pattern: publish the app on 127.0.0.1 and let a reverse proxy handle the public ports. Our guide to a Caddy reverse proxy with automatic HTTPS picks up exactly here. For a full workflow tool on this stack, see how to self-host n8n on a VPS; Uptime Kuma on a VPS uses the same localhost-plus-Caddy pattern for monitoring, and Nextcloud on a VPS runs your own file cloud from Nextcloud’s All-in-One image. Prefer deploying from Git through a dashboard? Coolify on a VPS does that, on a fresh server rather than on top of this setup.
How do you keep Docker and your containers updated?
Docker Engine
Docker’s upgrade instruction is to repeat the package install step, which pulls the newest packages from the repository:
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Do not count on automatic security updates here. Ubuntu’s Server docs state that adding a package repository does not make unattended-upgrades consider it; its default allowed origins are Ubuntu’s own release, security and ESM pockets. Docker 29.8.2, for example, fixed 13 CVEs across Docker Engine and BuildKit, and you only get those fixes by upgrading yourself, or by adding Docker’s origin to /etc/apt/apt.conf.d/50unattended-upgrades.
An upgrade restarts the daemon, which stops running containers until their restart policy starts them again. To keep containers running across patch upgrades, add "live-restore": true to daemon.json and run sudo systemctl reload docker. Docker’s live restore page supports this for patch releases only, such as 29.8.1 to 29.8.2, and warns that skipping releases can break it. To hold a specific version, list the available ones with apt list --all-versions docker-ce and install that version string, as Docker’s install page shows.
Container images
cd ~/stacks/pgdemo
docker compose pull
docker compose up -d
docker image prune
up -d re-creates only the services whose image or configuration changed, and it keeps mounted volumes. docker image prune removes dangling images left behind by the update. A major PostgreSQL upgrade, such as 18 to 19, is not a pull: it needs pg_upgrade or a dump and restore, so read the release notes first. If the database is the main job of the server, installing PostgreSQL on the VPS itself covers backups, remote access and major upgrades with Ubuntu’s own tools.
How much VPS do you need for Docker?
Docker Engine has no published RAM minimum on Linux. The 4 GB figure in Docker’s docs applies to Docker Desktop, which runs a VM. So size for the operating system plus your containers. Ubuntu 24.04 cloud images need at least 1 GB of RAM and 4 GB of disk, and Ubuntu’s suggested minimum is 3 GB of RAM and 25 GB of disk. PostgreSQL’s default shared_buffers is typically 128 MB.
| What you run | Starting plan | Monthly cap | Reasoning |
|---|---|---|---|
| Learning Docker; one or two small bot containers | Quartz Q1: 1 vCPU, 1 GB, 25 GB NVMe | $5.00/month cap | Meets Ubuntu’s 1 GB cloud-image minimum. No room for a database. |
| One small Compose stack (app, PostgreSQL, proxy) | Quartz Q2: 1 vCPU, 2 GB, 50 GB | $10.00/month cap | Leaves about 1 GB above the OS minimum for the app and PostgreSQL’s 128 MB buffer cache. A lean but workable choice. |
| Several stacks, n8n with a database, or building images on the server | Quartz Q4: 2 vCPU, 4 GB, 80 GB | $15.00/month cap | Meets Ubuntu’s 3 GB suggestion. docker build runs package installs that need extra memory and CPU while they run. |
| CI runners and frequent heavy builds | Chrono C8: 2 dedicated vCPU, 8 GB, 100 GB | $35.00/month cap | Dedicated vCPUs suit sustained CPU load. See ephemeral GitHub Actions runners on a VPS. |
Disk goes faster than you expect. On a fresh Docker 29 install, the containerd image store is the default. It keeps image layers both compressed and unpacked, so they take more space than under the older overlay2 driver, and Docker’s daemon data directory docs put them in /var/lib/containerd. Volumes stay in /var/lib/docker. Check docker system df now and then. Image pulls also count as traffic. Istanbul plans include the monthly traffic allowance listed for each plan, prorated for a server that exists for part of a billing period. To pick where to deploy, see how to choose a VPS location.
Cost: Practicing this guide on a Quartz Q2 for two hours costs $0.04. A stopped server is still billed, because its vCPU, memory, disk and IP addresses stay reserved for you; only deleting the server stops billing. An always-on Docker host can stay on the same server: you never pay more than the monthly price for a server in a billing period (one month from your order date), which is how a monthly VPS works here. The hourly vs monthly breakdown shows when the cap kicks in.
| Duration | Hours on the meter | Cost $0.03 | Note |
|---|---|---|---|
| 2 hours | 2 | $0.06 | |
| 1 day | 24 | $0.72 | |
| 7 days | 168 | $5.04 | |
| 30 days | 720 | $15.00 | Capped at the monthly price |
Check and troubleshoot Docker on a VPS
Six checks before you leave the server running
| Check | Command | Healthy result |
|---|---|---|
| Engine version | docker version --format '{{.Server. | A current 29.x release; never older than 28.0.0 (localhost port issue) |
| Starts at boot | systemctl is-enabled docker | enabled |
| Logs are capped | docker info --format '{{.LoggingDriver}}' | local, or json-file with max-size set |
| Only intended ports are public | docker ps --format 'table {{.Names}}\t{{.Ports}}' | 0. or [::] only on ports you mean to serve, such as 80 and 443 |
| Who has root-equivalent access | getent group docker | Only admin accounts you would give sudo |
| Disk headroom | df -h / && docker system df | Room to grow on /; if not, docker image prune and check log caps |
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
permission denied while trying to connect to the docker API at unix: | Your user is not in the docker group, or your session started before you were added | Use sudo, or join the group and log in again (see above) |
Cannot connect to the Docker daemon at unix: | The daemon is stopped or failed to start | sudo systemctl start docker, then read journalctl -xu docker. |
Docker will not start after you edit daemon. | Invalid JSON, an unknown key, or an option set both in the file and as a flag | sudo dockerd --validate --config-file=, fix, restart |
| apt warns a target is “configured multiple times” or fails with “Conflicting values set for option Signed-By” | An old docker. from an earlier guide next to docker. | Keep one file: check ls /etc/ and delete the old docker. |
| A container port answers from the internet although UFW denies it | Published ports bypass UFW | Bind to 127., remove the ports: entry, or filter in DOCKER-USER |
Disk fills up; /var/ keeps growing | json-file logs without rotation | Set the local driver in daemon., then re-create the containers |
| New log settings, but old containers still grow | Existing containers keep the log options they were created with | docker compose up -d --force-recreate |
| Containers do not come back after a reboot | No restart policy | Add restart: unless-stopped, then docker compose up -d |
A postgres: container exits with Error: in 18+, these Docker images are configured to store database data in a format… | A volume mounted at /var/ (the path for 17 and older), or data from an older major version | Mount the volume at /var/. Existing 17 data needs pg_ or a dump and restore, not just a new tag |
docker pull fails with You have reached your pull rate limit | Docker Hub allows 100 pulls per 6 hours per IPv4 address or IPv6 /64 without login | docker login with a Docker account; a Personal account gets 200 pulls per 6 hours (Docker Hub limits) |
docker-compose: command not found | Compose v1 is not installed; the plugin uses a space | Run docker compose … |
How to uninstall Docker completely
Warning: Step 2 deletes every image, container and volume, including database volumes like pgdata. Back up anything you need first.
- Remove the packages:
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
- Delete images, containers and volumes:
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
- Remove the repository, the key and the daemon configuration you created. Docker’s docs leave edited config files to you.
sudo rm /etc/apt/sources.list.d/docker.sources
sudo rm /etc/apt/keyrings/docker.asc
sudo rm /etc/docker/daemon.json
If the server only existed for this experiment, deleting the whole VPS is simpler and ends billing. Run through the checklist before you delete a VPS so nothing you need goes with it.
Deploy this setup
Run your Docker Compose stacks 24/7
Quartz Q4 · 2 shared vCPU · 4 GB RAM · 80 GB NVMe · Istanbul
- Per hour$0.03/hour
- Per day (24 h)$0.72/day
- Monthly cap$15.00/monthFor this job
Starts with a $5 initial credit, which goes into the server’s balance and pays for its hours.
Billed by the hour, never more than $15.00 per billing period. Delete the server and billing stops.



