Repository: basecamp/kamal (Ruby)

Issue:
Getting Bad Gateway on a fresh setup. When using dot in my service name. 
I was trying to set up a couple of instances with Kamal and was constantly getting Bad Gateway on all of them. I tried redeploying and spun up fresh ones as well.

With health checks passing, the `setup` and `deploy` commands were successful. 

I did some digging: 
- both containers are up
- docker inspect looked fine(to me).
- I even SSH'd into the `app` and `traefik` containers on the instance to make sure they could communicate with each other.
- when I tried `curl` from the `traefik` container, it worked without any issues.

**Traefik logs:** 
```bash
2024-02-07T17:09:17.248587866Z time="2024-02-07T17:09:17Z" level=debug msg="'502 Bad Gateway' caused by: dial tcp 172.17.0.2:0: connect: connection refused"
2024-02-07T17:12:43.194810142Z time="2024-02-07T17:12:43Z" level=debug msg="'502 Bad Gateway' caused by: dial tcp 172.17.0.2:0: connect: connection refused"
```

**~Solution**
I used a dot in the service name, like `app7.1`, which seemed harmless at first glance.
To fix it, I simply removed the dot from the service name, making it `app71`, and everything started working smoothly.
Just to be clear it's `config/deploy.yml:`
```yml
# Name of your application. Used to uniquely configure containers.
service: app71

```
**My theory** is that, although I'm not an expert in Docker routing and Traefik, the issue might be related to how the config files are named after the service name and Traefik/Docker labels. It seems like they could be causing some unintended issues.

Can someone confirm/deny it? If this is the case we can add it to documentation. 



Discussion:
Hi @tahirwaseer ,

You're correct in that a dot in the service name would cause an issue. 

In the documentation under configuration search "Using container labels" and you'll see: 
```Traefik rules are in the “service-role-destination” format. The default role will be web if no rule is specified. If the destination is not specified, it is not included. To give an example, the above rule would become “traefik.http.routers.hey-web-staging.rule” if it was for the “staging” destination.```

This sort of eludes to the reason why you get the error. The orchestration happening with Kamal, docker and Traefik breaks if you use a dot. Traefik leans heavily on dot notation for labels and passing in configuration. 

Hope this helps gain a bit of context.
Looks like it should be either handled by Kamal or explicitly forbidden.
@tahirwaseer Opened a PR to fail early if the service name contains dots in #692.



Patch:
diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb
index a3b074d54..04c870a8a 100644
--- a/lib/kamal/configuration.rb
+++ b/lib/kamal/configuration.rb
@@ -222,7 +222,7 @@ def allow_empty_roles?
 
 
   def valid?
-    ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version && ensure_retain_containers_valid
+    ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version && ensure_retain_containers_valid && ensure_valid_service_name
   end
 
   def to_h
@@ -287,6 +287,12 @@ def ensure_required_keys_present
       true
     end
 
+    def ensure_valid_service_name
+      raise ArgumentError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9-_]+$/
+
+      true
+    end
+
     def ensure_valid_kamal_version
       if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
         raise ArgumentError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"


Test patch:
diff --git a/test/configuration_test.rb b/test/configuration_test.rb
index fbc87a915..c538a82ab 100644
--- a/test/configuration_test.rb
+++ b/test/configuration_test.rb
@@ -42,6 +42,16 @@ class ConfigurationTest < ActiveSupport::TestCase
     end
   end
 
+  test "service name valid" do
+    assert Kamal::Configuration.new(@deploy.tap { _1[:service] = "hey-app1_primary" }).valid?
+  end
+
+  test "service name invalid" do
+    assert_raise(ArgumentError) do
+      Kamal::Configuration.new @deploy.tap { _1[:service] = "app.com" }
+    end
+  end
+
   test "roles" do
     assert_equal %w[ web ], @config.roles.collect(&:name)
     assert_equal %w[ web workers ], @config_with_roles.roles.collect(&:name)