5 min read

Docker lets you run applications in isolated containers. Each container has its own filesystem, network, and dependencies. No conflicts between apps, easy upgrades, and reproducible deployments. If you’re self-hosting anything on a GoZen VPS, Docker is the standard way to do it.

Installing Docker

SSH into your GoZen VPS (Ubuntu LTS recommended):

  # Remove old Docker packages if any
sudo apt remove docker docker-engine docker.io containerd runc 2>/dev/null

# Install Docker using the official script
curl -fsSL https://get.docker.com | sudo sh

# Add your user to the docker group (so you don't need sudo for every command)
sudo usermod -aG docker $USER

# Install Docker Compose plugin
sudo apt install docker-compose-plugin -y

# Log out and back in for group changes to take effect
exit
  

Verify the installation:

  docker --version
# Docker version 27.x.x

docker compose version
# Docker Compose version v2.x.x
  

Core Concepts

Image: a read-only template. Think of it as a snapshot of an application and all its dependencies. You pull images from Docker Hub.

Container: a running instance of an image. You can have multiple containers from the same image.

Volume: persistent storage. Container filesystems are temporary. Volumes survive container restarts and rebuilds.

Network: containers can talk to each other through Docker networks without exposing ports to the outside world.

Running Your First Container

  # Pull and run an Nginx web server
docker run -d --name my-nginx -p 8080:80 nginx

# -d: run in background (detached)
# --name: give it a name
# -p 8080:80: map port 8080 on the host to port 80 in the container
  

Visit http://your-server-ip:8080 to see the Nginx welcome page.

Managing Containers

  # List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# Stop a container
docker stop my-nginx

# Start a stopped container
docker start my-nginx

# View container logs
docker logs my-nginx

# Follow logs in real-time
docker logs -f my-nginx

# Execute a command inside a running container
docker exec -it my-nginx bash

# Remove a container (must be stopped first)
docker rm my-nginx

# Remove a container and stop it in one command
docker rm -f my-nginx
  

Docker Compose (The Real Way)

Running individual docker run commands gets messy. Docker Compose lets you define multiple containers in a single YAML file and manage them together.

Example: WordPress + MySQL + Redis

Create a project directory:

  mkdir ~/wordpress && cd ~/wordpress
  

Create docker-compose.yml:

  services:
  db:
    image: mariadb:11
    restart: always
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: change_this_root_pw
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: change_this_db_pw

  redis:
    image: redis:7-alpine
    restart: always

  wordpress:
    image: wordpress:latest
    restart: always
    ports:
      - "8080:80"
    volumes:
      - wp_data:/var/www/html
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: change_this_db_pw
    depends_on:
      - db
      - redis

volumes:
  db_data:
  wp_data:
  

Start everything:

  docker compose up -d
  

Docker pulls all images, creates the network, and starts the containers. WordPress is available at http://your-server-ip:8080.

Common Compose Commands

  # Start all services
docker compose up -d

# Stop all services
docker compose down

# Stop and remove volumes (deletes all data!)
docker compose down -v

# View logs for all services
docker compose logs

# View logs for a specific service
docker compose logs wordpress

# Restart a specific service
docker compose restart wordpress

# Pull updated images
docker compose pull

# Pull and restart with new images
docker compose pull && docker compose up -d

# Check status
docker compose ps
  

Volumes and Persistent Data

Containers are ephemeral. When you remove a container, its filesystem is gone. Volumes keep your data safe.

  # Named volume (Docker manages the location)
volumes:
  - db_data:/var/lib/mysql

# Bind mount (you choose the location)
volumes:
  - ./config:/etc/myapp/config
  

Named volumes are preferred for databases and application data. Docker manages them, and they’re easy to back up:

  # List all volumes
docker volume ls

# Inspect a volume (see where it's stored)
docker volume inspect wordpress_db_data

# Back up a volume (stop the database first so the copy is consistent)
docker compose stop db
docker run --rm -v wordpress_db_data:/data -v $(pwd):/backup alpine tar czf /backup/db-backup.tar.gz /data
docker compose start db
  

To back up a database without stopping it, take a logical dump instead:

  docker compose exec db sh -c 'mariadb-dump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases' > db-backup.sql
  

Networking

By default, Docker Compose creates a network for each project. Containers in the same Compose file can reach each other by service name:

  # WordPress connects to the database using hostname "db"
WORDPRESS_DB_HOST: db
  

To expose a container to the outside world, map ports:

  ports:
  - "8080:80"            # host:container, reachable from the internet
  - "127.0.0.1:8081:80"  # reachable only from the server itself (for a reverse proxy)
  

Only expose ports you need. Databases should almost never be exposed: leave out the ports: line and let other containers reach them over the Docker network.

Cleaning Up

Docker accumulates images, stopped containers, and unused volumes:

  # Remove all stopped containers
docker container prune

# Remove unused images
docker image prune

# Remove unused volumes (careful - this deletes data)
docker volume prune

# Nuclear option: remove everything unused
docker system prune -a
  

Security Basics

  • Don’t run containers as root inside the container when possible. Many images support running as a non-root user
  • Don’t expose database ports to the internet. Keep them internal to the Docker network
  • Keep images updated: docker compose pull && docker compose up -d
  • Use specific image tags instead of latest in production (e.g., mariadb:11.8 not mariadb:latest)
  • Set resource limits to prevent one container from consuming all server resources:
  services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
  

What to Do Next

Last updated 21 Oct 2026, 00:00 +0300. history

Was this page helpful?