Despliega Windmill en Pethost con un clic
Scripts, flujos y apps en Python, TypeScript, Go, SQL o Bash, ejecutados por workers según un horario, un webhook o un botón
Qué pasa
- Inicias sesión en Pethost o creas una cuenta, con una passkey, GitHub o Google.
- ¿Aún no tienes máquina? Eliges un plan y te preparamos una. El enlace espera en tu navegador hasta que esté lista.
- Nuevo proyecto se abre en la app. Rellenas lo que pide y haces clic en Crear proyecto: Correo electrónico, Contraseña de administrador.
- En un par de minutos la app está en marcha en su propia dirección con HTTPS. Sus secretos se generan al azar para ella, y sus datos entran en el backup de cada noche.
- Es un proyecto como cualquier otro: su compose.yaml, su .env, sus logs y un terminal están en el panel, y tu agente puede cambiarlo, volver a una versión anterior o restaurarlo.
Primeros pasos
Entra con el correo y la contraseña de administrador que indicaste: Windmill crea ese superadministrador en el primer arranque. Crea un workspace y luego un script o un flujo; sus paquetes se instalan en la primera ejecución.
Qué ejecuta
La plantilla es este compose.yaml y las variables que escribe en el .env del proyecto. Pethost ejecuta el archivo tal como está escrito.
services:
windmill_server:
image: ghcr.io/windmill-labs/windmill:1.835.0
restart: unless-stopped
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db/windmill?sslmode=disable
MODE: server
BASE_URL: ${URL}
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
# A new Windmill signs anyone in as [email protected] / changeme. So, before it answers on its
# own port, it is started on one nobody reaches, the email and password asked are made its
# superadmin, and the stock account is deleted.
command:
- sh
- -c
- |
PORT=8001 windmill &
setup=$$!
node -e '
const base = "http://127.0.0.1:8001/api";
const json = { "Content-Type": "application/json" };
const login = (email, password) => fetch(base + "/auth/login", { method: "POST", headers: json, body: JSON.stringify({ email, password }) });
const replaceStock = async (token) => {
const auth = { ...json, Authorization: "Bearer " + token };
const account = { email: process.env.ADMIN_EMAIL, password: process.env.ADMIN_PASSWORD, super_admin: true, name: "Admin" };
await fetch(base + "/users/create", { method: "POST", headers: auth, body: JSON.stringify(account) });
const mine = await login(account.email, account.password);
if (!mine.ok) throw new Error("the new superadmin cannot sign in: " + (await mine.text()));
const gone = await fetch(base + "/users/delete/[email protected]", { method: "DELETE", headers: { Authorization: "Bearer " + (await mine.text()) } });
if (!gone.ok) throw new Error("the stock account stays: " + (await gone.text()));
};
const run = async () => {
for (let i = 0; i < 300; i++) {
try {
const stock = await login("[email protected]", "changeme");
if (stock.status === 400 || stock.status === 401) return;
if (stock.ok) return await replaceStock(await stock.text());
} catch (error) {
if (String(error).includes("Error:")) console.error(String(error));
}
await new Promise((done) => setTimeout(done, 1000));
}
process.exit(1);
};
run().catch((error) => { console.error(String(error)); process.exit(1); });
' || exit 1
kill $$setup
wait $$setup
exec windmill
volumes:
- worker_logs:/tmp/windmill/logs
depends_on:
db:
condition: service_healthy
windmill_worker:
image: ghcr.io/windmill-labs/windmill:1.835.0
restart: unless-stopped
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db/windmill?sslmode=disable
MODE: worker
WORKER_GROUP: default
volumes:
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
depends_on:
db:
condition: service_healthy
# Runs the light "native" jobs (HTTP and SQL steps) in process.
windmill_worker_native:
image: ghcr.io/windmill-labs/windmill:1.835.0
restart: unless-stopped
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db/windmill?sslmode=disable
MODE: worker
WORKER_GROUP: native
NATIVE_MODE: "true"
SLEEP_QUEUE: "200"
volumes:
- worker_logs:/tmp/windmill/logs
depends_on:
db:
condition: service_healthy
db:
image: postgres:18
restart: unless-stopped
shm_size: 1g
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: windmill
volumes:
- db_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
volumes:
db_data:
worker_dependency_cache:
worker_logs: