-
-
Notifications
You must be signed in to change notification settings - Fork 35
blog: Tailscale for DDEV: Simple and Secure Project Sharing #423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
52d083d
feat: add ddev-tailscale-router add-on documentation for enhanced dev…
atj4me 514b7f8
docs: clarify private and public mode descriptions in ddev-tailscale-…
atj4me 2c80b8f
feat: add author and blog post for ddev-tailscale-router add-on docum…
atj4me 3b2d56e
feat: update author bio to include PHP-based applications and modern …
atj4me d62a160
fix: add missing newline at end of author bio file
atj4me 8dba949
feat: update documentation for ddev-tailscale-router add-on with clea…
atj4me a5720ec
fix: improve clarity and consistency in ddev-tailscale-router add-on …
atj4me 1c6a146
feat: add DDEV Tailscale Router flowchart and update blog post with i…
atj4me 0a17fbb
Add AI Attribution
atj4me 58f1c04
fix: update title and improve clarity in Tailscale for DDEV blog post
atj4me 4b23ff0
fix: add exclamation mark for clarity in Tailscale add-on description
atj4me 662b61b
Adding a note that it isn't been tested on MacOS
atj4me c8e6c19
Addon support added for macos
atj4me c40e131
Updating the article based on the suggestions
atj4me da22897
feat: add ddev-tailscale-router add-on documentation for enhanced dev…
atj4me 4788ed8
docs: clarify private and public mode descriptions in ddev-tailscale-…
atj4me 1e2f669
feat: add author and blog post for ddev-tailscale-router add-on docum…
atj4me 5b55bb5
feat: update author bio to include PHP-based applications and modern …
atj4me db78fd7
fix: add missing newline at end of author bio file
atj4me c461342
feat: update documentation for ddev-tailscale-router add-on with clea…
atj4me e7ebe8d
fix: improve clarity and consistency in ddev-tailscale-router add-on …
atj4me 7c4486e
feat: add DDEV Tailscale Router flowchart and update blog post with i…
atj4me dc6364e
Add AI Attribution
atj4me 68ec269
fix: update title and improve clarity in Tailscale for DDEV blog post
atj4me 5b34d66
fix: add exclamation mark for clarity in Tailscale add-on description
atj4me 27f859b
Adding a note that it isn't been tested on MacOS
atj4me 8b970fe
Addon support added for macos
atj4me cb6bb9b
Updating the article based on the suggestions
atj4me d7c8181
Adding Prerequisites section to make things clear
atj4me 7889aa5
Fix Merge Conflicts
atj4me 20405c7
Adding relevant resources
atj4me 358ab1b
Adding section about new commands
atj4me aa98995
Changing URLs and adding new resources
atj4me 00165dc
Adding a note to mention Tailscale ACL for funnel
atj4me 88ec5d2
Updating the blog to reflect the addon readme changes
atj4me 2f00c7e
Replacing admin panel urls with documentations
atj4me b835dc2
Fixing typos
atj4me c0aaaa6
Update pubdate
rfay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| name: Ajith Thampi Joseph | ||
| firstName: Ajith | ||
| avatarUrl: https://gravatar.com/avatar/3381b988b59552300f08ff9f6d8918f2?s=128 | ||
| --- | ||
|
|
||
| [Ajith](https://www.linkedin.com/in/thampiajit) is a Software Engineer specializing in PHP-based applications (WordPress, Drupal, Laravel) and modern frontend technologies (React, TypeScript). He has extensive experience with Linux web servers, Docker, CI/CD pipelines, and DevOps practices across multiple Git platforms. His work focuses on creating developer tools that eliminate workflow friction and improve team collaboration efficiency. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,371 @@ | ||
| --- | ||
| title: "Streamlining Development Workflows with ddev-tailscale-router" | ||
| pubDate: 2025-08-26 | ||
| summary: Learn how the ddev-tailscale-router add-on extends DDEV's local development capabilities with secure networking, enabling cross-device testing, stable webhook endpoints, and seamless team collaboration. | ||
| author: Ajith Thampi Joseph | ||
| categories: | ||
| - Guides | ||
| - TechNotes | ||
| --- | ||
|
|
||
| [DDEV](https://ddev.com) has transformed local web development by providing containerized environments that eliminate "works on my machine" problems. Its architecture uses `.ddev.site` domains and intelligent routing to prevent port conflicts, enabling multiple projects to run simultaneously. This capability is essential for modern development workflows. | ||
|
|
||
| While DDEV excels at local development, today's practices increasingly involve external service integrations requiring stable webhook endpoints, OAuth callbacks, and cross-device testing scenarios. The [`ddev-tailscale-router`](https://github.com/atj4me/ddev-tailscale-router) add-on extends DDEV's capabilities by seamlessly connecting development environments to secure, private networks. | ||
|
|
||
| ## Extending DDEV's Capabilities for Modern Development Needs | ||
|
|
||
| DDEV's containerized architecture brilliantly solves local development complexity through its reverse proxy (ddev-router) system. This design enables multiple projects to coexist without port conflicts, which is essential for modern full-stack development where teams often run frontend, backend, and admin applications simultaneously. | ||
|
|
||
| **DDEV's Architectural Strengths:** | ||
|
|
||
| - `.ddev.site` domain routing prevents port conflicts across multiple projects | ||
| - Container isolation ensures clean, reproducible development environments | ||
| - Multi-project support enables complex application architectures | ||
| - Minimal configuration local development with automatic service discovery | ||
|
|
||
| **Modern Development Requirements:** | ||
| Today's development workflows extend beyond single-machine scenarios: | ||
|
|
||
| **External Service Integration Needs:** | ||
|
|
||
| - **Webhook Development**: Payment processors like [Stripe](https://stripe.com/docs/webhooks) and [PayPal](https://developer.paypal.com/docs/api-basics/notifications/webhooks/) require stable, internet-accessible endpoints for testing transaction flows | ||
| - **OAuth Implementation**: Social login providers such as [Google](https://developers.google.com/identity/protocols/oauth2), [GitHub](https://docs.github.com/en/developers/apps/building-oauth-apps), and [Facebook](https://developers.facebook.com/docs/facebook-login/) need consistent callback URLs that persist across development sessions | ||
| - **API Integration**: Third-party services require reliable endpoint addresses for testing and development | ||
|
|
||
| **Individual Developer and Team Requirements:** | ||
|
|
||
| - **Cross-Device Testing**: Mobile and tablet testing requires accessing DDEV projects from various devices | ||
| - **Client Demonstrations**: Stakeholders benefit from accessing work-in-progress through stable URLs | ||
| - **Team Collaboration**: For teams with paid Tailscale plans, secure sharing of development environments becomes possible | ||
|
|
||
| **Multi-Project Development Scenarios:** | ||
| Decoupled applications typically involve multiple DDEV projects running simultaneously, such as frontend, backend API, and admin dashboard components. While DDEV handles this complexity locally, extending access for cross-device testing and external service integration requires additional networking solutions. | ||
|
|
||
| ### Available Approaches and Considerations | ||
|
|
||
| #### DDEV's Built-in Sharing Capabilities | ||
|
|
||
| DDEV provides excellent built-in sharing through the [`ddev share`](https://ddev.readthedocs.io/en/stable/users/topics/sharing/) command, leveraging [ngrok](https://ngrok.com) for instant internet exposure: | ||
|
|
||
| **Strengths:** | ||
|
|
||
| - One-command activation with `ddev share` (requires ngrok installation and account setup) | ||
| - Automatic HTTPS certificate provisioning | ||
| - Seamless integration with DDEV's workflow once configured | ||
|
|
||
| **Considerations for Extended Use:** | ||
|
|
||
| - Free-tier provides one static subdomain (configured via ngrok config), but it's a long, non-memorable URL | ||
| - Random URLs are generated when not using the static subdomain configuration | ||
| - Webhook and OAuth integrations benefit from more memorable, persistent URLs | ||
| - Multi-project architectures may require multiple tunnel instances | ||
|
|
||
| #### DDEV's Local Network Sharing | ||
|
|
||
| DDEV supports local network access through [router configuration](https://docs.ddev.com/en/stable/users/topics/sharing/#exposing-a-host-port-and-providing-a-direct-url): | ||
|
|
||
| ```bash | ||
| ddev config global --router-bind-all-interfaces | ||
| ``` | ||
|
|
||
| **Benefits:** | ||
|
|
||
| - Enables local network access to DDEV projects | ||
| - Maintains DDEV's security model | ||
| - No external service dependencies | ||
| - Works well for same-network device testing | ||
|
|
||
| **Considerations:** | ||
|
|
||
| - Requires DNS configuration on other devices for `.ddev.site` resolution | ||
| - Limited to devices on the same local network | ||
| - May need firewall adjustments for broader access | ||
|
|
||
| #### Enterprise VPN Solutions | ||
|
|
||
| Traditional VPN infrastructure provides comprehensive network access: | ||
|
|
||
| **Capabilities:** | ||
|
|
||
| - Full network-level access control | ||
| - Enterprise-grade security and auditing | ||
| - Comprehensive device management | ||
|
|
||
| **Implementation Considerations:** | ||
|
|
||
| - Server infrastructure and maintenance requirements | ||
| - Client configuration complexity for team members | ||
| - Ongoing security and certificate management | ||
|
|
||
| For development-focused scenarios, lighter weight solutions often provide better developer experience. | ||
|
|
||
| ## Tailscale: A Modern Networking Solution | ||
|
|
||
| [Tailscale](https://tailscale.com) represents a modern approach to VPN technology, offering zero configuration mesh networking built on [WireGuard's](https://www.wireguard.com/) cryptographic foundation. This approach addresses many traditional VPN limitations: | ||
|
|
||
| **Simplified Setup Process** | ||
| Tailscale eliminates complex server provisioning by providing [client applications](https://tailscale.com/download) that authenticate through existing identity providers like Google, GitHub, and Microsoft. No certificate management or network configuration is required. | ||
|
|
||
| **Secure Mesh Networking** | ||
| Devices automatically form encrypted peer-to-peer connections, creating a private network that spans different physical locations and network environments. | ||
|
|
||
| **Intuitive Addressing** | ||
| [MagicDNS](https://tailscale.com/kb/1081/magicdns/) provides human readable hostnames (such as `device-name.tailnet-name.ts.net`) instead of requiring IP address memorization. This transforms development workflows by providing memorable, stable URLs that work seamlessly across all devices on the Tailscale network. | ||
|
|
||
| **Flexible Access Control** | ||
| [Tailscale Funnel](https://tailscale.com/kb/1223/tailscale-funnel/) enables selective public exposure of services when needed while maintaining private network security by default. | ||
|
|
||
| **The DDEV-Tailscale Integration Opportunity** | ||
|
|
||
| Tailscale's mesh networking approach perfectly complements DDEV's local development strengths: | ||
|
|
||
| - **Enhances DDEV's Multi-Project Capabilities**: Each DDEV project receives its own Tailscale machine with stable hostname addressing | ||
| - **Extends DDEV's Reach**: Developers can securely access development environments from any network location | ||
| - **Complements DDEV's Architecture**: Works alongside DDEV's existing container ecosystem without disrupting local workflows | ||
| - **Supports Modern Development Patterns**: Enables secure access to decoupled applications and microservice architectures | ||
| - **Maintains DDEV's Simplicity**: Provides complex networking capabilities through simple configuration | ||
|
|
||
| The integration creates a bridge between DDEV's excellent local development experience and the connectivity requirements of modern development workflows. MagicDNS provides intuitive, memorable URLs that eliminate the complexity of IP address management. | ||
|
|
||
| ## Introducing ddev-tailscale-router | ||
|
|
||
| The `ddev-tailscale-router` add-on bridges DDEV's local development capabilities with Tailscale's networking infrastructure. This integration provides a streamlined approach to development environment sharing without requiring networking expertise. | ||
|
|
||
| **Core Functionality** | ||
|
|
||
| The add-on establishes Tailscale connectivity for DDEV projects through dedicated container deployment: | ||
|
|
||
| - **Per-Project Containers**: Each DDEV project gets its own Tailscale container with unique hostname | ||
| - **Direct Proxying**: Uses socat to proxy requests directly from Tailscale to the web container | ||
| - **Zero Configuration Changes**: Compatible with existing DDEV projects without modification | ||
| - **Independent Operation**: Works alongside DDEV's existing networking without interference | ||
| - **Granular Privacy Controls**: Supports both private (Tailnet only via Tailscale Serve) and public (internet accessible via Tailscale Funnel) access modes | ||
|
|
||
| ### Technical Architecture | ||
|
|
||
| The add-on implements a per-project Tailscale container approach that works independently of DDEV's router: | ||
|
|
||
| 1. **Container Orchestration**: Each DDEV project gets its own dedicated Tailscale container | ||
| 2. **Network Authentication**: Automatic connection to designated Tailscale network using provided authentication keys | ||
| 3. **Traffic Proxying**: socat proxies incoming Tailscale requests directly to the project's web container | ||
| 4. **Address Stability**: Each project receives its own persistent Tailscale hostname via MagicDNS | ||
| 5. **Lifecycle Management**: Tailscale containers start and stop with their respective DDEV projects | ||
|
|
||
| ### Integration Benefits | ||
|
|
||
| **Complements DDEV's Built-in Sharing:** | ||
|
|
||
| - Provides persistent URLs ideal for webhook and OAuth development | ||
| - Maintains stable endpoints across development session restarts | ||
| - Supports multi-project architectures with individual addressing | ||
|
|
||
| **Enhances Individual Development and Team Collaboration:** | ||
|
|
||
| - Extends DDEV's single-machine excellence to multi-device accessibility | ||
| - Private mode (default): tailnet-only access via Tailscale Serve for secure development | ||
| - Public mode: internet-wide access via Tailscale Funnel for client demos and external testing | ||
|
|
||
| **Strengthens Security Model:** | ||
|
|
||
| - Maintains private network principles with optional public exposure | ||
| - Provides granular access control through Tailscale's ACL system | ||
| - Enables secure sharing without compromising development environment isolation | ||
|
|
||
| ## Implementation Guide | ||
|
|
||
| ### Installation Process | ||
|
|
||
| The add-on installation follows [DDEV's standard add-on management](https://ddev.readthedocs.io/en/stable/users/extend/additional-services/#ddev-add-ons) workflow: | ||
|
|
||
| ```bash | ||
| ddev add-on get atj4me/ddev-tailscale-router | ||
| ``` | ||
|
|
||
| ### Authentication Configuration | ||
|
|
||
| Tailscale integration requires authentication key generation through the administrative interface: | ||
|
|
||
| 1. Access the [Tailscale admin console](https://login.tailscale.com/admin/settings/keys) | ||
| 2. Generate a new authentication key (prefix: `tskey-auth-`) | ||
| 3. Configure the key within the DDEV project environment: | ||
|
|
||
| ```bash | ||
| ddev dotenv set .ddev/.env.tailscale-router --ts-authkey=tskey-auth-your-key-here | ||
| ddev restart | ||
| ``` | ||
|
|
||
| ### Service Access | ||
|
|
||
| Once configured, development sites become accessible through Tailscale's MagicDNS hostnames: | ||
|
|
||
| 1. Navigate to the [Tailscale admin console](https://login.tailscale.com/admin/machines) | ||
| 2. Locate the machine entry corresponding to your DDEV project | ||
| 3. Access the development site using the human-readable hostname | ||
|
|
||
| For instance, a project named `myapp` becomes available at `http://myapp-ddev-web.your-tailnet.ts.net`, eliminating the need to remember IP addresses. | ||
|
|
||
| ## Practical Applications | ||
|
|
||
| ### Webhook and OAuth Development | ||
|
|
||
| Stable endpoint addressing transforms external service integration workflows: | ||
|
|
||
| **Payment Webhook Development:** | ||
|
|
||
| ``` | ||
| # Stable endpoint for Stripe webhook configuration | ||
| http://myapp-ddev-web.your-tailnet.ts.net/webhooks/stripe | ||
|
|
||
| # PayPal webhook endpoint | ||
| http://myapp-ddev-web.your-tailnet.ts.net/webhooks/paypal | ||
|
|
||
| # Square webhook endpoint | ||
| http://myapp-ddev-web.your-tailnet.ts.net/webhooks/square | ||
|
|
||
| # Human-readable, memorable URLs across development sessions | ||
| ``` | ||
|
|
||
| **OAuth Callback Configuration:** | ||
|
|
||
| ``` | ||
| # Google OAuth callback | ||
| http://myapp-ddev-web.your-tailnet.ts.net/auth/google/callback | ||
|
|
||
| # GitHub OAuth callback | ||
| http://myapp-ddev-web.your-tailnet.ts.net/auth/github/callback | ||
|
|
||
| # Facebook OAuth callback | ||
| http://myapp-ddev-web.your-tailnet.ts.net/auth/facebook/callback | ||
|
|
||
| # Microsoft OAuth callback | ||
| http://myapp-ddev-web.your-tailnet.ts.net/auth/microsoft/callback | ||
| ``` | ||
|
|
||
| This stability eliminates the need for repeated URL updates in external service dashboards, streamlining the development and testing of payment flows and authentication systems. | ||
|
|
||
| **For External Testing**: When webhooks need to be accessible from the internet (like Stripe's webhook testing), switch to public mode instead of deploying to a staging server: | ||
|
|
||
| ```bash | ||
| # Enable internet access for webhook testing | ||
| ddev dotenv set .ddev/.env.tailscale-router --ts-privacy=public | ||
| ddev restart | ||
|
|
||
| # Test your webhooks with the public URL | ||
| # https://myapp-ddev-web.your-tailnet.ts.net/webhooks/stripe | ||
|
|
||
| # Switch back to private mode when testing is complete | ||
| ddev dotenv set .ddev/.env.tailscale-router --ts-privacy=private | ||
| ddev restart | ||
| ``` | ||
|
|
||
| ### Cross-Device Testing | ||
|
|
||
| The add-on simplifies mobile testing by eliminating manual DNS configuration: | ||
|
|
||
| - **Private Mode**: Install Tailscale mobile app for secure cross device access | ||
| - **Public Mode**: No app required, access from any internet connected device | ||
|
|
||
| ### Multi-Project Development | ||
|
|
||
| Each DDEV project gets its own Tailscale hostname, enabling access to complete application stacks: | ||
|
|
||
| ``` | ||
| # Traditional: frontend.ddev.site (local only) | ||
| # With Tailscale: frontend-ddev-web.your-tailnet.ts.net (accessible everywhere) | ||
| ``` | ||
|
|
||
| ## Access Control: Two Distinct Modes | ||
|
|
||
| The add-on provides two access modes through a simple configuration setting: | ||
|
|
||
| ### Private Mode (Default - Tailscale Serve) | ||
|
|
||
| By default, development sites use Tailscale Serve for Tailnet-only access: | ||
|
|
||
| ```bash | ||
| ddev dotenv set .ddev/.env.tailscale-router --ts-privacy=private | ||
| ddev restart | ||
| ``` | ||
|
|
||
| **Use Cases:** | ||
|
|
||
| - Cross-device testing within your Tailnet | ||
| - Team collaboration with Tailnet members | ||
| - Internal development and testing | ||
| - Secure development workflows | ||
|
|
||
| ### Public Mode (Tailscale Funnel) | ||
|
|
||
| For internet-wide access, the add-on uses Tailscale Funnel: | ||
|
|
||
| ```bash | ||
| ddev dotenv set .ddev/.env.tailscale-router --ts-privacy=public | ||
| ddev restart | ||
| ``` | ||
|
|
||
| **Use Cases:** | ||
|
|
||
| - Client demonstrations and feedback sessions | ||
| - External webhook testing (Stripe, PayPal, etc.) | ||
| - OAuth provider configuration and testing | ||
| - Third-party API integration testing | ||
| - User acceptance testing with external stakeholders | ||
| - Mobile app testing that requires backend APIs | ||
|
|
||
| **Why Funnel Outperforms Traditional Staging Deployments:** | ||
|
|
||
| Most developers rely on staging servers for external testing, but Tailscale Funnel offers significant advantages: | ||
|
|
||
| - **Instant Exposure**: Enable or disable public access in seconds | ||
| - **Environment Consistency**: Test exactly what you're developing locally | ||
| - **Zero Infrastructure**: Eliminates staging servers and deployment pipelines | ||
| - **Cost Effective**: No additional hosting costs | ||
| - **Security Control**: Easily revoke access when testing is complete | ||
|
|
||
| ## Technical Considerations | ||
|
|
||
| ### Platform Compatibility | ||
|
|
||
| The add-on currently supports Linux and Windows with [WSL2](https://learn.microsoft.com/en-us/windows/wsl/). Support for macOS and ARM64 (Apple Silicon) platforms is under active development. | ||
|
|
||
| ### Protocol Support | ||
|
|
||
| The add-on provides both HTTP and HTTPS access to development sites. Tailscale Serve (private mode) and Tailscale Funnel (public mode) both automatically provide HTTPS endpoints with valid certificates, eliminating the need for manual certificate configuration. | ||
|
|
||
| ### Performance Characteristics | ||
|
|
||
| Tailscale's WireGuard-based routing introduces minimal latency overhead that's typically imperceptible during development. Network performance remains well-suited for standard development workflows, including real-time debugging and asset reloading. | ||
|
|
||
| ## Troubleshooting Guide | ||
|
|
||
| ### URL Discovery Issues | ||
|
|
||
| Verify project machine registration in the [Tailscale admin console](https://login.tailscale.com/admin/machines) and confirm hostname assignment. MagicDNS hostnames follow the pattern `[project-name]-ddev-web.[tailnet-name].ts.net`. | ||
|
|
||
| ### Application Display Problems | ||
|
|
||
| Some applications that generate `localhost` or `.ddev.site` URLs may require base URL configuration updates to use Tailscale hostnames instead. This is particularly common in CMS applications like [WordPress](https://wordpress.org/support/article/changing-the-site-url/) and [Drupal](https://www.drupal.org/docs/administering-a-drupal-site/site-configuration/changing-the-site-url), as well as frameworks that store base URLs in configuration files or databases. | ||
|
|
||
| ### Network Connectivity Issues | ||
|
|
||
| If you cannot access your development site through the Tailscale hostname, verify that: | ||
|
|
||
| - The Tailscale container is running: `ddev logs -s tailscale-router` | ||
| - Your authentication key is valid and properly configured | ||
| - MagicDNS is enabled in your Tailscale network settings | ||
| - The project name matches the hostname pattern in the admin console | ||
|
|
||
| ### Authentication Failures | ||
|
|
||
| Confirm authentication key validity and proper environment variable configuration through [DDEV's dotenv system](https://ddev.readthedocs.io/en/stable/users/extend/customization-extendibility/#providing-custom-environment-variables-to-a-container). | ||
|
|
||
| ## Support and Documentation | ||
|
|
||
| Comprehensive support resources are available through multiple channels: | ||
|
|
||
| - **Issue Tracking**: [GitHub repository](https://github.com/atj4me/ddev-tailscale-router/issues) for bug reports and feature requests | ||
| - **Community Support**: [DDEV Discord community](https://discord.gg/hCZFzjGGtP) for implementation assistance | ||
| - **Documentation**: [Complete setup guide](https://github.com/atj4me/ddev-tailscale-router) with configuration examples | ||
| - **DDEV Documentation**: [Official DDEV docs](https://ddev.readthedocs.io/) for general DDEV usage and troubleshooting | ||
|
|
||
| The add-on is actively maintained, and community contributions are welcomed through the open-source repository. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.