Docs
General
Docker

Guides

Docker host access from container

First you need this line in your service

extra_hosts:
	- host.docker.internal:host-gateway

Second, anywhere you use the URI needs to have "host.docker.internal" rather than "localhost" or the IP.

Third, ufw needs to have access to the network subnet. To do this first check which network was created by compose for this using docker network ls. Then use docker network inspect <name>. After this take the subnet under IPAM and run the following. The host port is the port you're trying to forward from the localhost to the docker container.

sudo ufw allow from <subnet-w-cidr> to any port <host-port>

Expose container only via Tailscale

Because Docker deals with iptables straight rather than going through UFW, any firewall rules set wouldn't affect a docker mapped port. To ensure your Docker container is only accessible via Tailscale and not via the host's IP address, you can configure a Docker container to only listen on the Tailscale network interface.

Find the Tailscale IP Address
On the Docker host, run tailscale ip to get the Tailscale IP address (e.g., 100.x.x.x).

Modify Docker Daemon Configuration
Create or edit the Docker daemon configuration file, usually located at /etc/docker/daemon.json. Add the following configuration to bind Docker to the Tailscale network interface:

{ "hosts": ["fd://", "tcp://100.x.x.x:2375"] }

This binds Docker to the Tailscale IP on port 2375 (you can use any available port).

Override default Configuration in Systemd
Edit the Docker service configuration file and remove the hosts flag:

sudo systemctl edit docker
### Anything between...
[Service]
ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock
### Lines below...

Restart the Docker daemon to apply the changes

sudo systemctl restart docker

Run Docker Container Bound to Tailscale Interface
When you run your Docker container, bind it to the Tailscale IP and the desired port (e.g., 5001):

docker run -p 100.x.x.x:5001:5001
 your-docker-image

Now configure the firewall. Unless the default rule is deny (incoming) block it using sudo ufw deny 5001

Compose Files

PHP Server

This is a simple PHP server with Nginx as a reverse proxy. The PHP server is running on port 9000 and Nginx is running on port 8080. The PHP server is using the php:8.2-fpm image and the Nginx server is using the nginx:latest image. The PHP server is mounted to the ./html/ directory and the Nginx server is mounted to the ./nginx/conf.d/ directory.

    • docker-compose.yml
      • index.php
        • default.conf
  • docker-compose.yml
    version: "3"
    name: php-server
    services:
      nginx:
        image: nginx:latest
        ports:
          - "8080:80" # local:container
        networks:
          - internal
        volumes:
          - ./html/:/var/www/html/ # local:container
          - ./nginx/conf.d/:/etc/nginx/conf.d/
      php:
        image: php:8.2-fpm
        networks:
          - internal
        volumes:
          - ./html/:/var/www/html/
    nginx/conf.d/nginx.conf
    server {
        listen 0.0.0.0:80;
        root /var/www/html;
        location / {
            index index.php index.html;
        }
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass php:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
        }
    }

    MySQL Server

    This is a simple MySQL server running on port 3306. The MySQL server is mounted to the ./mysql/ directory.

    docker-compose.yml
    version: "3"
    name: mysql-server
    services:
      db:
        image: mysql:8.0
        ports:
          - "3306:3306" # local:container
        networks:
          - internal
        volumes:
          - ./mysql/:/var/lib/mysql/ # local:container
        environment:
          MYSQL_ROOT_PASSWORD: root # set the root password