Skip to content

Commit 52bf8b8

Browse files
committed
templating (cloud-init, ansible, compose) support
1 parent 5359a35 commit 52bf8b8

20 files changed

Lines changed: 1254 additions & 8 deletions

docs/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default defineConfig({
7373
{ text: "OpenClaw", link: "/guides/openclaw" },
7474
{ text: "Hermes", link: "/guides/hermes" },
7575
{ text: "VSCodium Web IDE", link: "/guides/vscode" },
76+
{ text: "Templating Containers with trm launch", link: "/guides/templating" },
7677
{ text: "Isolated Docker Compose deployments", link: "/guides/compose" },
7778
{ text: "Dokploy", link: "/guides/dokploy" },
7879
{ text: "Coolify", link: "/guides/coolify" },

docs/getting-started/creating-first-instance.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ You can choose whether you want to work through the browser or the terminal. Bot
2626
If you prefer the terminal, you can create the exact same container in one command. SSH into your Terrarium host and run:
2727

2828
```bash
29-
lxc launch ubuntu:24.04 my-first-app
29+
trm launch ubuntu:24.04 my-first-app
3030
```
3131

3232
*(By default, Terrarium automatically applies its secure container profile, which puts the container on the private `terrarium-ovn` network and gives it its own isolated ZFS storage.)*
@@ -43,7 +43,7 @@ For this example, let's just run a tiny, temporary web server.
4343

4444
**From the CLI:**
4545
```bash
46-
lxc exec my-first-app -- bash
46+
trm exec my-first-app
4747
```
4848

4949
Once you are inside the container's shell, run this command to start a simple Python web server on port `8080`:
@@ -82,6 +82,12 @@ On the Terrarium host terminal (not inside the container), run:
8282
lxc config set my-first-app user.proxy "https://my-app.example.com:8080"
8383
```
8484

85+
You can also set the route during launch:
86+
87+
```bash
88+
trm launch ubuntu:24.04 my-first-app --proxy https://my-app.example.com:8080
89+
```
90+
8591
## 4. Applying the Change
8692

8793
Terrarium scans your containers for new proxy labels automatically every 60 seconds. If you don't want to wait a minute for your new site to come online, you can force it to sync immediately.

docs/guides/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ You can run databases, internal APIs, and experimental AI agents without worryin
1919
- **[VSCodium Web IDE](vscode.md):** The absolute best way to code in the cloud. Spin up a browser-based editor that is completely isolated and secured by SSO.
2020
- **[OpenClaw](openclaw.md):** Give the powerful autonomous AI agent a safe, disposable sandbox to execute code in.
2121
- **[Hermes](hermes.md):** Run agent-driven background services and expose only the user interface to the web.
22+
- **[Templating Containers with `trm launch`](templating.md):** Use Ansible, Galaxy roles, Docker Compose, variables, and proxy labels to launch ready-to-use containers in one command.
2223
- **[Docker Compose Stacks](compose.md):** The cleanest way to run multi-container apps (like a web server + Postgres + Redis) without making a mess of your host machine.
2324
- **[Dokploy](dokploy.md):** Turn your Terrarium containers into an automated, UI-driven deployment platform (like Heroku or Vercel).
2425
- **[Coolify](coolify.md):** Another excellent, self-hosted Heroku alternative that runs perfectly inside a single Terrarium LXC.
25-
- **[Protecting Services with OIDC](auth-protection.md):** The magic label that forces users to log in before they can see your published web apps.
26+
- **[Protecting Services with OIDC](auth-protection.md):** The magic label that forces users to log in before they can see your published web apps.

docs/guides/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ You can run databases, internal APIs, and experimental AI agents without worryin
1919
- **[VSCodium Web IDE](vscode.md):** The absolute best way to code in the cloud. Spin up a browser-based editor that is completely isolated and secured by SSO.
2020
- **[OpenClaw](openclaw.md):** Give the powerful autonomous AI agent a safe, disposable sandbox to execute code in.
2121
- **[Hermes](hermes.md):** Run agent-driven background services and expose only the user interface to the web.
22+
- **[Templating Containers with `trm launch`](templating.md):** Use Ansible, Galaxy roles, Docker Compose, variables, and proxy labels to launch ready-to-use containers in one command.
2223
- **[Docker Compose Stacks](compose.md):** The cleanest way to run multi-container apps (like a web server + Postgres + Redis) without making a mess of your host machine.
2324
- **[Dokploy](dokploy.md):** Turn your Terrarium containers into an automated, UI-driven deployment platform (like Heroku or Vercel).
2425
- **[Coolify](coolify.md):** Another excellent, self-hosted Heroku alternative that runs perfectly inside a single Terrarium LXC.
25-
- **[Protecting Services with OIDC](auth-protection.md):** The magic label that forces users to log in before they can see your published web apps.
26+
- **[Protecting Services with OIDC](auth-protection.md):** The magic label that forces users to log in before they can see your published web apps.

docs/guides/templating.md

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Templating Containers with `trm launch`
2+
3+
`trm launch` is the fastest way to turn a fresh LXD container into a real application environment.
4+
5+
It wraps `lxc launch`, so simple containers still feel familiar. When you add provisioning flags, Terrarium generates a small cloud-init template for the container, embeds or fetches the files you asked for, and runs the setup inside the new instance.
6+
7+
Use it when you want a container to come up already shaped like an app server, agent sandbox, Docker Compose stack, or Ansible-managed machine.
8+
9+
## 1. Start with a Normal Container
10+
11+
The simplest form launches an Ubuntu container with Terrarium's default profile:
12+
13+
```bash
14+
trm launch ubuntu:24.04 web-01
15+
```
16+
17+
You can still pass normal LXD sizing options:
18+
19+
```bash
20+
trm launch ubuntu:24.04 web-01 --disk 40G --memory 4G --cpu 2
21+
```
22+
23+
For development containers, use the `dev` profile. It includes Terrarium's Docker-friendly defaults and passwordless sudo for the normal `terrarium` user:
24+
25+
```bash
26+
trm launch ubuntu:24.04 devbox --profile dev
27+
```
28+
29+
## 2. Run an Ansible Playbook
30+
31+
If you already have an Ansible playbook, pass it directly:
32+
33+
```bash
34+
trm launch ubuntu:24.04 web-01 --playbook ./site.yml
35+
```
36+
37+
Terrarium embeds the local playbook into generated cloud-init, installs Ansible inside the container, and runs:
38+
39+
```bash
40+
ansible-playbook -i localhost, -c local site.yml
41+
```
42+
43+
A small real-world playbook might install Nginx and publish a static page:
44+
45+
```yaml
46+
---
47+
- hosts: localhost
48+
connection: local
49+
become: true
50+
tasks:
51+
- name: Install Nginx
52+
ansible.builtin.apt:
53+
name: nginx
54+
state: present
55+
update_cache: true
56+
57+
- name: Write landing page
58+
ansible.builtin.copy:
59+
dest: /var/www/html/index.html
60+
content: "{{ app_name }} is live\n"
61+
```
62+
63+
Launch it with a variable and a public route:
64+
65+
```bash
66+
trm launch ubuntu:24.04 landing-01 \
67+
--playbook ./site.yml \
68+
--var app_name=Landing \
69+
--proxy https://landing.example.com:80
70+
```
71+
72+
## 3. Add Galaxy Requirements
73+
74+
If your playbook depends on Galaxy roles or collections, pass the requirements file too:
75+
76+
```bash
77+
trm launch ubuntu:24.04 app-01 \
78+
--requirements ./requirements.yml \
79+
--playbook ./site.yml
80+
```
81+
82+
Requirements can contain `roles:`, `collections:`, or both. Terrarium installs them before running the playbook.
83+
84+
For a quick role-only container, use `--role`:
85+
86+
```bash
87+
trm launch ubuntu:24.04 docker-01 --role geerlingguy.docker
88+
```
89+
90+
You can repeat `--role` when you want several roles:
91+
92+
```bash
93+
trm launch ubuntu:24.04 ops-01 \
94+
--role geerlingguy.security \
95+
--role geerlingguy.nginx
96+
```
97+
98+
## 4. Run Docker Compose
99+
100+
For Compose apps, pass one or more Compose files:
101+
102+
```bash
103+
trm launch ubuntu:24.04 my-stack \
104+
--profile dev \
105+
--docker-compose ./docker-compose.yml
106+
```
107+
108+
Terrarium installs Docker inside the container, starts the Docker service, and runs:
109+
110+
```bash
111+
docker compose -f docker-compose.yml up -d
112+
```
113+
114+
Here is a simple app stack:
115+
116+
```yaml
117+
services:
118+
web:
119+
image: nginx:alpine
120+
ports:
121+
- "8080:80"
122+
```
123+
124+
Publish it during launch:
125+
126+
```bash
127+
trm launch ubuntu:24.04 nginx-stack \
128+
--profile dev \
129+
--docker-compose ./docker-compose.yml \
130+
--proxy https://nginx.example.com:8080
131+
```
132+
133+
Remember that Compose ports are still inside the LXD container. Terrarium routes public traffic to the container port you put in the `--proxy` route.
134+
135+
## 5. Use Variables
136+
137+
Variables keep the same template reusable across staging, production, and one-off test containers.
138+
139+
Pass variables inline:
140+
141+
```bash
142+
trm launch ubuntu:24.04 app-01 \
143+
--playbook ./site.yml \
144+
--var APP_NAME=hello \
145+
--var APP_PORT=8080
146+
```
147+
148+
Or keep them in a dotenv file:
149+
150+
```dotenv
151+
APP_NAME=hello
152+
APP_PORT=8080
153+
POSTGRES_DB=app
154+
```
155+
156+
Then launch with:
157+
158+
```bash
159+
trm launch ubuntu:24.04 app-01 \
160+
--vars ./app.env \
161+
--playbook ./site.yml \
162+
--docker-compose ./docker-compose.yml
163+
```
164+
165+
Variables are made available in three places:
166+
167+
- As shell environment variables for generated provisioning commands.
168+
- As Ansible extra vars through `--extra-vars`.
169+
- As Docker Compose variables through `--env-file`.
170+
171+
Inline values override dotenv files:
172+
173+
```bash
174+
trm launch ubuntu:24.04 app-staging \
175+
--vars ./app.env \
176+
--var APP_NAME=staging \
177+
--docker-compose ./docker-compose.yml
178+
```
179+
180+
The dotenv parser supports normal `KEY=value` lines, quoted values, `export KEY=value`, blank lines, and comments. Variable names must use shell-safe names such as `APP_NAME`, `POSTGRES_DB`, or `PORT_8080`.
181+
182+
## 6. Fetch Templates from Git
183+
184+
Local files are embedded into cloud-init. Git assets are cloned by the new container during first boot.
185+
186+
Use this format:
187+
188+
```text
189+
git+https://github.com/org/repo.git//path/inside/repo.yml?ref=v1.0.0
190+
```
191+
192+
For example:
193+
194+
```bash
195+
trm launch ubuntu:24.04 app-01 \
196+
--requirements git+https://github.com/example/infra.git//ansible/requirements.yml?ref=v1.2.0 \
197+
--playbook git+https://github.com/example/infra.git//ansible/site.yml?ref=v1.2.0
198+
```
199+
200+
Compose files work the same way:
201+
202+
```bash
203+
trm launch ubuntu:24.04 plausible \
204+
--profile dev \
205+
--vars ./plausible.env \
206+
--docker-compose git+https://github.com/example/stacks.git//plausible/docker-compose.yml?ref=main \
207+
--proxy https://analytics.example.com:8000@auth:admins
208+
```
209+
210+
Pin `ref` to a tag or commit when you want repeatable launches.
211+
212+
## 7. Combine Multiple Templates
213+
214+
You can combine the provisioning shortcuts. Terrarium runs them in this order:
215+
216+
1. Install packages needed for provisioning.
217+
2. Clone Git assets.
218+
3. Install Galaxy requirements.
219+
4. Install and run Galaxy roles.
220+
5. Run Ansible playbooks.
221+
6. Start Docker and run Compose stacks.
222+
223+
A practical full launch might look like this:
224+
225+
```bash
226+
trm launch ubuntu:24.04 customer-portal \
227+
--profile dev \
228+
--disk 80G \
229+
--memory 6G \
230+
--cpu 4 \
231+
--vars ./portal.env \
232+
--var APP_ENV=production \
233+
--requirements ./ansible/requirements.yml \
234+
--playbook ./ansible/base.yml \
235+
--playbook ./ansible/hardening.yml \
236+
--docker-compose ./compose/portal.yml \
237+
--docker-compose ./compose/worker.yml \
238+
--proxy https://portal.example.com:8080@auth:admins \
239+
--proxy https://api.example.com:8081@auth:admins
240+
```
241+
242+
This creates one isolated container, prepares it with Ansible, starts two Compose stacks, and publishes two authenticated HTTPS routes.
243+
244+
## 8. Use Raw Cloud-Init When You Need Full Control
245+
246+
If you already have a complete cloud-init file, pass it directly:
247+
248+
```bash
249+
trm launch ubuntu:24.04 raw-01 --cloud-init ./user-data.yml
250+
```
251+
252+
Raw cloud-init replaces Terrarium's generated template. Because of that, it cannot be combined with `--requirements`, `--playbook`, `--role`, `--docker-compose`, `--var`, or `--vars`.
253+
254+
You can still combine it with normal launch options and proxy labels:
255+
256+
```bash
257+
trm launch ubuntu:24.04 raw-01 \
258+
--cloud-init ./user-data.yml \
259+
--disk 40G \
260+
--proxy https://raw.example.com:8080
261+
```
262+
263+
## 9. Debugging a Launch
264+
265+
Cloud-init runs after LXD reports that the container was created. If the instance exists but your app is not ready yet, open a shell:
266+
267+
```bash
268+
trm exec app-01
269+
```
270+
271+
Useful logs inside the container:
272+
273+
```bash
274+
sudo cloud-init status --long
275+
sudo tail -n 200 /var/log/cloud-init-output.log
276+
sudo journalctl -u docker --no-pager -n 100
277+
```
278+
279+
For Compose apps:
280+
281+
```bash
282+
sudo docker compose ps
283+
sudo docker compose logs --tail=100
284+
```
285+
286+
If the app is running but the public route does not respond, check that the service listens on `0.0.0.0` inside the container and then sync the proxy from the host:
287+
288+
```bash
289+
terrariumctl proxy sync
290+
```
291+
292+
For the full route label grammar, including authenticated routes and wildcard domains, see [Domains and Authentication](../getting-started/domains-and-auth.md#published-app-route-labels).

0 commit comments

Comments
 (0)