Thanks for helping improve these Tailscale sidecar examples.
-
Copy the service template from the repository root. Replace
my-servicewith your service name:cp -R templates/service-template services/my-service
This command includes the hidden
.envfile. Use a lowercase directory name. -
Update
.envwith safe example values.Set
SERVICE,IMAGE_URL,SERVICEPORT, and the application variables. Never commit a working auth key, password, token, or other credential. -
Adapt
compose.yaml.- Keep the Compose service keys
tailscaleandapplication. - Name the containers
tailscale-${SERVICE}andapp-${SERVICE}. - Keep
network_mode: service:tailscaleon the application. - Keep the application's health-based dependency on
tailscale. - Add all required persistent volumes.
- Add required devices and capabilities explicitly.
- Keep the Compose service keys
-
Set the Serve proxy to the application's internal port.
The Serve JSON does not read
SERVICEPORTfrom.env. Keep runtime variables escaped, such as$${TS_CERT_DOMAIN}.Keep the
portsblock commented for Tailnet-only access. Document any LAN port you expose. Remove the Serve configuration when the service does not use Tailscale Serve. -
Configure the application's health check.
Use the first option that the image supports. The template lists the same options in the same order.
- When the image defines its own
HEALTHCHECK, omit the block and add a# Healthcheck: defined by the image (...)comment that names the command or what it checks. Override the image's check only when it does not work with the stack, and say why in a comment. - Call an application endpoint with a client that ships with the image.
- Run a health command that ships with the image.
- Check a fixed process name with
pidoforpgrep -x, when the container has no endpoint or the image has no HTTP client.pgrep -xmatches at most 15 characters of the process name. When the process name is generic, such aspython, usepgrep -fwith a fixed pattern. - When the image has no shell, HTTP client, or health command, no check
is possible. Omit the block and add a
# Healthcheck: none possible ...comment that names the reason.
Run the command inside the running container before you commit it. Avoid
pgrep -f ${SERVICE}, which breaks whenSERVICEis renamed.A container that runs once and exits needs no check. Omit the block and add a
# Healthcheck: none needed ...comment that names the reason.For a database, connect over TCP, for example with
pg_isready -h 127.0.0.1ormariadb-admin ping -h 127.0.0.1. During the first start, the image runs a temporary server that accepts only socket connections, so a socket check reports ready too early. When another service depends on the database, wait withcondition: service_healthy. A database image's ownHEALTHCHECKstill comes first. When that check connects over the socket, say so in the comment.Keep the Tailscale health check.
- When the image defines its own
-
Complete the service README.
Document prerequisites, persistent paths, setup steps, ports, Tailnet access, and service-specific exceptions. Link to the upstream documentation.
-
Add the service to the correct category in the root
README.md.Keep the entries in that category alphabetized.
- Read the service README and Compose file before you make changes.
- Preserve the shared network namespace and Tailscale dependency.
- Preserve persistent volumes unless you document a safe migration.
- Use
${VARIABLE}for Compose interpolation, not$(VARIABLE). - Update the service README when ports, paths, setup, or behavior change.
- Update the root service list when you add, remove, or rename a service.
Preserve valid service-specific exceptions.
Run Compose validation from each changed service directory:
docker compose config --quietThis command does not prove that the application works.
When possible, start the stack and confirm:
- Tailscale becomes healthy and joins the Tailnet.
- The application starts and is reachable through the Tailnet.
- The application's main function works.
- Persistent storage and documented LAN access work, when applicable.
Follow the pull request template. Report the checks you ran and any checks you could not run.