Skip to content

Garage: New Service - #340

Closed
Spectre7651 wants to merge 0 commit into
tailscale-dev:mainfrom
Spectre7651:main
Closed

Spectre7651 wants to merge 0 commit into
tailscale-dev:mainfrom
Spectre7651:main

Conversation

@Spectre7651

Copy link
Copy Markdown

Garage: New Service

Description

Created a service for running Garage with a Tailscale sidecar including the default garage.toml configuration file required
Also removed whitespace from linting config file due to code editor warnings

Related Issues

  • None.

Verification

Tested by running both locally (Mac) and on a Raspberry Pi 5 (Bookworm 64bit) with latest versions of docker installed
Containers run, connect to tailnet and application is accessible via S3 API

Checklist

  • I have performed a self-review of my code and followed the templates structure.
  • I have added verification that the stack works as expected.
  • I have updated necessary documentation (e.g. frontpage README.md ).
  • I have selected the correct label(s) for this PR.

Additional Context

  • garage.toml file is required to run the container so have included it in this PR
  • This is my first PR so any feedback is more than welcome

@jackspiering

Copy link
Copy Markdown
Collaborator

Thanks for your first PR, @Spectre7651, and welcome! Garage is a great addition. I tested this branch locally. As submitted it does not start yet, but after a few fixes it worked well: an S3 upload and download through Tailscale Serve over HTTPS succeeded, and the bucket survived a restart. Here is what needs to change, based on CONTRIBUTING.md and the service template:

Blockers (the stack does not start)

  1. Serve JSON is invalid. The ts-serve block is missing braces, so the Tailscale container crash-loops with failed to read serve config: invalid character ':' after object key:value pair. Copy the block from the template and only change the port:
    configs:
      ts-serve:
        content: |
          {"TCP":{"443":{"HTTPS":true}},
          "Web":{"$${TS_CERT_DOMAIN}:443":
              {"Handlers":{"/":
              {"Proxy":"http://127.0.0.1:3900"}}}},
          "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}}
  2. Secrets are literal text. rpc_secret = "$(openssl rand -hex 32)" is not run by any shell, so Garage stops with Invalid RPC secret key: expected 32 bytes of random hex. The admin and metrics tokens would become publicly known strings. I suggest removing the three secrets from garage.toml and passing them from .env instead. Garage reads GARAGE_RPC_SECRET, GARAGE_ADMIN_TOKEN, and GARAGE_METRICS_TOKEN; I tested this. Leave them empty in .env with a comment on how to generate them (openssl rand -hex 32 and openssl rand -base64 32).
  3. Data is not persisted. garage.toml stores metadata and data in /tmp/meta and /tmp/data, but the volumes are mounted at /var/lib/garage/meta and /var/lib/garage/data, so everything is lost when the container is recreated. Point metadata_dir and data_dir to the mounted paths.

Health check

The image contains only the /garage binary, with no shell and no pgrep, so the current check can never pass. CONTRIBUTING also asks to avoid pgrep -f ${SERVICE}. Option 3 from the template works here, and I tested it:

healthcheck:
  test: ["CMD", "/garage", "status"] # Check that the Garage node responds over RPC

Keep the template's interval, timeout, retries, and start_period values.

Template and repository conventions

  • Please revert the change to .markdownlint.yml. It is unrelated to Garage. The lint errors you saw were in the new README and are already fixed there.
  • Add Garage to the right category in the root README.md, sorted alphabetically (CONTRIBUTING step 7).
  • Remove PUID, PGID, and TZ from the application's environment, because Garage does not use them. Keep the TZ line in .env.
  • Use the template's .env layout, with comments on their own lines, and fix the varibale typo.
  • Optional: mount the data as ./${SERVICE}-data/meta and ./${SERVICE}-data/data to match the other services.

README

Please add the setup steps a new user needs:

  • generating the secrets
  • garage layout assign and garage layout apply (Garage stores nothing until a layout is applied)
  • creating a key and bucket with docker exec app-garage /garage ...
  • the S3 endpoint https://garage.<tailnet>.ts.net with region garage

Also mention that only the S3 API (3900) is published through Serve, and that the web (3902) and admin (3903) ports are not. Link to the upstream quick start.

Tip for next time: open PRs from a feature branch instead of your fork's main. That way you can keep your main in sync with upstream.

Thanks again! Happy to take another look once it's updated.

@Spectre7651

Spectre7651 commented Oct 3, 2026 •

Copy link
Copy Markdown
Author

Thanks @jackspiering for the feedback, I'll implement those changes asap

I've also switched to a feature branch as you recommended hence why this PR closed - sorry for the inconvenience

Would you be able to offer any tips on exposing multiple ports with the Tailscale serve config as ports 3900, 3901, 3902 and 3903 all need to be exposed and I'm not sure on how to do that with the existing serve config

Many Thanks

@jackspiering

Copy link
Copy Markdown
Collaborator

Good question, @Spectre7651. Serve can publish more than one port: add one entry per port under TCP and one under Web. I tested this with Garage v2.3.0:

configs:
  ts-serve:
    content: |
      {"TCP":{"443":{"HTTPS":true},"3902":{"HTTPS":true},"3903":{"HTTPS":true}},
      "Web":{"$${TS_CERT_DOMAIN}:443":
          {"Handlers":{"/":
          {"Proxy":"http://127.0.0.1:3900"}}},
        "$${TS_CERT_DOMAIN}:3902":
          {"Handlers":{"/":
          {"Proxy":"http://127.0.0.1:3902"}}},
        "$${TS_CERT_DOMAIN}:3903":
          {"Handlers":{"/":
          {"Proxy":"http://127.0.0.1:3903"}}}},
      "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}}

One change in garage.toml is required for this to work. Tailscale opens its own listener on the Serve ports, so Garage cannot also bind 3902 and 3903 on [::]. Garage still logs "listening", but the port is not open and Serve returns 502. Bind those two to loopback instead:

[s3_web]
bind_addr = "127.0.0.1:3902"

[admin]
api_bind_addr = "127.0.0.1:3903"

That gives you:

Port Purpose Address
3900 S3 API https://garage.<tailnet>.ts.net
3902 Static websites https://garage.<tailnet>.ts.net:3902
3903 Admin API and metrics https://garage.<tailnet>.ts.net:3903

Two notes:

  • 3901 (RPC) does not belong in Serve. It is not HTTP and is only used between Garage nodes, so a single node with replication_factor = 1 does not need it. Leave rpc_bind_addr as it is.
  • 3902 (web) picks the bucket from the Host header. Through Serve, that means Garage serves the bucket named garage.<tailnet>.ts.net, with website access enabled on it.

Please list these three addresses in the README. This replaces my earlier remark that only the S3 API is published through Serve.

@Spectre7651 Spectre7651 mentioned this pull request Oct 4, 2026
4 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants