Setup Guide for Coder with Tailscale Tunneling

Setup Guide for Coder with Tailscale Tunneling

This guide provides a step-by-step setup for integrating Coder with Tailscale tunneling in a Docker environment. Ensure your Docker setup meets the outlined assumptions before proceeding.

Assumptions

  • Your Docker setup is not using the 172.20/16 IP range.
  • Update the network configuration in networks.tailnet.ipam.config.0 to an unused network if necessary.
  • Set the environment variable TS_ROUTES=your-network/24 to adjust routing for the Tailscale network.
  • You are not running rootless Docker or Podman.
  • Traefik is configured in your stack:
    • Attached to a Docker network named servicenet.
    • Consumes Docker container labels to build the service catalog.
    • Has a TLS certificate resolver named default-le (set TRAEFIK_TLS_RESOLVER=<your resolver name> if different).

Required Environment Variables

Before you start, ensure you have the following environment variables defined:

  • FRONTEND_HOST=<HTTP host to listen on (e.g., code.example.org)>
  • CODER_ACCESS_URL=<full HTTPS URL (e.g., https://code.example.org/)>
  • TS_AUTHKEY=<get a Tailscale auth key>
  • CODER_VERSION=v0.23.7

Docker Compose Configuration

Create a docker-compose.yml file with the following content:

version: "3.9"

networks:
  backend: {}
  outbound: {}
  tailnet:
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/16
          ip_range: 172.20.0.0/24
          gateway: 172.20.0.1
  servicenet:
    external: true
    
volumes:
  coder-data:
    external: true
  tailscale-data: {}

services:
  coder:
    image: ghcr.io/coder/coder:${CODER_VERSION:-latest}
    environment:
      CODER_PG_CONNECTION_URL: "postgresql://${POSTGRES_USER:-username}:${POSTGRES_PASSWORD:-password}@database/${POSTGRES_DB:-coder}?sslmode=disable"
      CODER_ADDRESS: "0.0.0.0:7080"
      # You'll need to set CODER_ACCESS_URL to an IP or domain
      # that workspaces can reach. This cannot be localhost
      # or 127.0.0.1 for non-Docker templates!
      CODER_ACCESS_URL: "${CODER_ACCESS_URL}"
    # If the coder user does not have write permissions on
    # the docker socket, you can uncomment the following
    # lines and set the group ID to one that has write
    # permissions on the docker socket.
    group_add:
      - "997" # docker group on host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      database:
        condition: service_healthy
    networks:
      - servicenet
      - backend
    labels:
      - "appname=coder"
      - "traefik.enable=true"
      - "traefik.http.routers.coder.rule=Host(`${FRONTEND_HOST}`)"
      - "traefik.http.routers.coder.entrypoints=web-secure"
      - "traefik.http.routers.coder.service=coder"
      - "traefik.http.routers.coder.tls=true"
      - "traefik.http.routers.coder.tls.certResolver=${TRAEFIK_TLS_RESOLVER:-default-le}"
      - "traefik.http.services.coder.loadbalancer.server.port=7080"
      - "traefik.http.services.coder.loadbalancer.passhostheader=true"

  database:
    image: "postgres:14.2"
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-username} # The PostgreSQL user (useful to connect to the database)
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} # The PostgreSQL password (useful to connect to the database)
      POSTGRES_DB: ${POSTGRES_DB:-coder} # The PostgreSQL default database (automatically created at first launch)
    volumes:
      - coder-data:/var/lib/postgresql/data # Use "docker volume rm coder-data" to reset Coder
    networks:
      - backend
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "pg_isready -U ${POSTGRES_USER:-username} -d ${POSTGRES_DB:-coder}",
        ]
      interval: 5s
      timeout: 5s
      retries: 5

  tailscale:
    image: "tailscale/tailscale:stable"
    cap_add:
      - NET_ADMIN
      - NET_RAW
    environment:
      TS_AUTHKEY: "${TS_AUTHKEY}"
      TS_ACCEPT_DNS: "${TS_ACCEPT_DNS:-true}"
      TS_EXTRA_ARGS: "${TS_EXTRA_ARGS}"
      TS_ROUTES: "${TS_ROUTES:-172.20.0.0/24}"
      TS_USERSPACE: "${TS_USERSPACE:-false}"
    volumes:
      - tailscale-data:/var/lib
    devices:
      - /dev/net/tun:/dev/net/tun
    sysctls:
      net.ipv4.ip_forward: 1
      net.ipv6.conf.all.forwarding: 1
    networks:
      - outbound
      - tailnet

Steps to Deploy

  1. Create the Docker Network:
    Ensure that your Docker network named servicenet is created and configured properly.

  2. Run Docker Compose:
    Execute the following command in the directory containing your docker-compose.yml file:

    docker-compose up -d
    
  3. Authenticate Tailscale:
    After starting your services, you will need to authenticate your Tailscale container. You can find the authentication link in the logs of the Tailscale container:

    docker logs tailscaled
    
  4. Enable Route Settings:
    Log into your Tailscale admin console and enable the advertised routes corresponding to your Docker network.

  5. Access Coder:
    You can now access Coder through the specified frontend host URL, which should be routed through Tailscale.

Additional Configuration

  • Traefik Configuration:
    Ensure that Traefik is properly configured to utilize Docker labels for routing and that it listens on the correct ports specified in your environment variables.

  • Testing Connectivity:
    Use tools like curl or browser access to verify that you can reach your Coder instance via Tailscale.

Troubleshooting Tips

  • If you encounter issues accessing services, check the logs of both the Coder and Tailscale containers for any errors.
  • Ensure that your firewall settings allow traffic through the necessary ports.
  • Verify that all environment variables are set correctly before running Docker Compose.

By following this guide, you should have a functional setup of Coder with Tailscale tunneling, allowing secure access to your development environment over a private network.