Tutorial 16: Build and deploy a production application
Objective: take the to-do app from Tutorials 8–10 to production: validate it, build it, run it on PostgreSQL behind HTTPS, deploy new releases safely, and back it up.
Prerequisites: Tutorial 9 (PostgreSQL). For option A, a cPanel hosting account or a Linux server/VPS with SSH; a Linux server or VM for options B and C; any hosting with a File Manager for option D.
Step 1: Validate
iwc validate1/4 files and references
✓ 24 files · 7 screens · 7 apis · 2 schemas
2/4 tests
✓ 13 test(s) passed
3/4 production build
✓ tasks 1.0.0 builds (7 pages, 7 apis)
4/4 security
✓ no findings
✓ valid in 1.02svalidate stops at the first failing stage. Run it in CI on every change.
Step 2: Production configuration checklist
| Item | Where |
|---|---|
| Database URL | TASKS_DATABASE_URL in the server environment (app.iwc names it) |
| Encryption key, if you use encrypted fields | IWC_ENCRYPTION_KEY (iwc secrets set IWC_ENCRYPTION_KEY --generate, then iwc secrets export) |
| Provider credentials | the {"env": …} names in .integration.iwc files |
| Simulated services | replaced with real providers — production refuses to start with them |
| Canonical address | app.iwc seo.siteUrl, for sitemaps, canonical links and payment return URLs |
| Admin accounts | created through your data, with two-factor sign-in (auth.mfa) |
| Security audit | iwc security --strict passes |
.env files are never packaged or uploaded. Set secrets in the server's environment.
Step 3: Build
iwc build --reportdist/ contains the compiled manifest.json, assets/, migrations/sqlite.sql and migrations/postgres.sql (for review), openapi.json/openapi.yaml, release.json and report.json. --report prints what each route sends against its size budget.
Step 4, option A: iwc deploy (recommended)
iwc deploy prodThe first run asks where the app will run — shared hosting with cPanel or your own server/VPS — and walks you through it:
- cPanel: your cPanel address, username and an API token (cPanel → Security → Manage API Tokens → Create). Pick one of the account's domains, or let IWC create a subdomain such as
tasks.example.com. - Own server: its address, SSH user and port. Compare the host-key fingerprint it shows with your provider's console, then type the server password once so IWC can install your SSH key. The first deploy installs the service, the web server and HTTPS.
Set the database on the server: on cPanel, TASKS_DATABASE_URL=postgres://… in settings.env in the site's folder (File Manager); on your own server, iwc server env prod set TASKS_DATABASE_URL.
→ deploying 1.0.0 3f9c2a7e1b04c5d6 to tasks.example.com
✓ release 1.0.0 3f9c2a7e1b04c5d6 is in place (database and settings.env kept)
✓ https://tasks.example.com answers 200 and the app's health check is okCheck on it any time with iwc server status prod and iwc logs prod.
Step 4, option B: run it yourself
export TASKS_DATABASE_URL="postgres://tasks:…@db.internal:5432/tasks"
iwc migrate --dry-run # review
iwc start dist --host 127.0.0.1 --port 8080 --trust-proxy loopbackProduction mode means: no seed rows, simulated services refused, Secure cookies and HSTS. Put a TLS proxy in front — for example Caddy:
tasks.example.com {
reverse_proxy 127.0.0.1:8080
}Health check: GET /_iwc/health.
Step 4, option C: your own release agent
On the server, once:
IWC_DEPLOY_TOKEN=<32+ random characters> TASKS_DATABASE_URL=postgres://… iwc host --dir /srv/iwc-host --port 8080In package.iwc on your machine:
{
"name": "tasks",
"version": "1.0.0",
"iwc": 1,
"deploy": { "prod": { "url": "https://deploy.tasks.example.com", "tokenEnv": "IWC_DEPLOY_TOKEN" } }
}Then, from your machine:
iwc deploy prod # build, upload; activated only if migrations succeed
iwc logs prod # recent structured logs
iwc rollback prod # reactivate the previous release
iwc pull prod copy/ # the live source and dist (never secrets)A release whose migrations fail never goes live, and rollback is atomic.
Step 4, option D: upload a zip by hand
iwc build --zip --folder tasksOn cPanel hosting, option A does all of this for you.
Upload tasks.zip in the File Manager, extract it in public_html, and open https://yourdomain.com/tasks/. The zip holds a CGI build of the runtime; the database is created on the first visit (SQLite in data/, or PostgreSQL via settings.env). For scheduled jobs on a quiet site, add a cron job: /home/<user>/public_html/tasks/iwc.cgi run-jobs.
Step 5: Back up
iwc backup dist --out /var/backups/tasks-$(date +%F).iwcbak.tar.gz --keep 14On PostgreSQL the backup is one consistent snapshot taken while the app runs. Restore with iwc restore <archive> dist (into an empty database) or --replace. A SQLite backup restores into PostgreSQL and the other way round.
Step 6: Ship a change
- Change files,
iwc validate. - Bump
versioninpackage.iwc. iwc deploy prod.- If something is wrong,
iwc rollback prodputs the previous release back — on your own server and on cPanel hosting. The database is kept.
New tables and fields migrate automatically; a destructive change is refused before anything is written, with the reason.
Troubleshooting
| Problem | Fix |
|---|---|
iwc start refuses to start | A required environment variable (database URL, provider credentials) is missing, or a simulated service is still configured. |
cPanel refused the login (HTTP 401) | Check the username; create a new API token. The next iwc deploy asks again. |
THE SERVER'S HOST KEY HAS CHANGED | The server was reinstalled, or the connection is intercepted. Confirm the new fingerprint on the provider's console first. |
the deploy token (IWC_DEPLOY_TOKEN) must be at least 32 characters | Option C: use a longer random token on both ends. |
| Shared hosting shows "This site could not start" | The reason is in data/logs/app.log; iwc.cgi and folders must be 755. |
| Cookies are not kept over plain HTTP in testing | Production cookies are Secure; test with --secure=false only on a trusted network. |
More: Deployment, Security, Performance & budgets.
Source: apps/marketing/content/docs/tutorials/deploy.md