🧰 Build a Test Cloud with DevStack: From a Blank Ubuntu to a Working Cloud
How to install a complete OpenStack test cloud on one machine with DevStack: the stack user, local.conf, the 18-minute install, what it changed on the machine, how to check it, and what survives a reboot (and the two small fixes that make everything come back)
💡 How this page was made. The whole install was run for real on 8 October 2026 in a throwaway virtual machine (QEMU/KVM, the official Ubuntu 24.04 cloud image, 4 CPUs, 8 GB of RAM, a 58 GB disk and 4 GB of swap) using DevStack's stable/2026.1 branch (the "Gazpacho" release from the previous page). The numbers (time, downloads, disk, memory) are from that run, on one machine with a fast connection; yours will differ. The commands for creating the stack user and the warnings about passwords and production use come from the DevStack documentation, read the same day. Things not run are marked.
What DevStack is, and what it is not
DevStack is, in its own documentation's words, "a series of extensible scripts used to quickly bring up a complete OpenStack environment based on the latest versions of everything from git master." You give it one small file (local.conf), run one script (stack.sh), and it installs the packages, downloads the OpenStack source code, writes the configuration for every service, starts them, and creates a first set of users, networks and images.
It is meant for learning, development and testing, and the documentation is blunt about it: "DevStack will make substantial changes to your system during installation. Only run DevStack on servers or virtual machines that are dedicated to this purpose."
⚠️ Use a machine you can throw away. A virtual machine is ideal: you can snapshot it, break it and rebuild it. Never run DevStack on your own laptop's operating system, or on a server that does anything else. And never expose a DevStack cloud to the internet: its passwords are in a text file and its design is for convenience, not for hardening. For a cloud that has to last, people use packaged installers (see the end of this page).
The documentation recommends Ubuntu 24.04 (Noble) as the most tested system, which is what this page uses. The default services are the ones from the previous page: Keystone, Glance, Nova, Placement, Cinder, Neutron and Horizon.
What you need
On the machine used here:
- CPU: 4 virtual CPUs. Memory: 8 GB, plus 4 GB of swap. With one tiny instance running, the cloud used about 5.4 GB of the 8 GB. Smaller sizes were not tested.
- Disk: the root disk went from 5.7 GB used to about 12 GB during the install, and a 10 GB volume file was added on top (see below, it starts almost empty). A 58 GB disk was plenty.
- Network: the install downloaded roughly 1.4 GB. The network card's receive counter read 1,378,209,990 bytes at the end of the install, which includes everything since the machine booted (package lists, packages, source code, Python packages and the test image).
- Hardware virtualisation inside the VM. Instances in the cloud are virtual machines of their own, so a VM running DevStack needs nested virtualisation: the host must pass the CPU's virtualisation feature through, and /dev/kvm must exist inside the VM. The host here was an AMD processor with nested virtualisation on (/sys/module/kvm_amd/parameters/nested reads 1) and the VM saw /dev/kvm. If you have no /dev/kvm, set LIBVIRT_TYPE=qemu in local.conf: instances still run, only much slower (not tested here).
Step 1: a user called stack
DevStack must not run as root, and it refuses. This is the real message, produced by running it as root on purpose (the script stops right there and changes nothing):
$ sudo ./stack.sh (run as root, on purpose; the trace lines starting with + are left out) DevStack should be run as a user with sudo permissions, not root. A "stack" user configured correctly can be created with: /home/stack/devstack/tools/create-stack-user.sh exit status: 1
So create a normal user with sudo rights. These are the commands from the DevStack documentation:
sudo useradd -s /bin/bash -d /opt/stack -m stack sudo chmod +x /opt/stack echo "stack ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/stack sudo su stack && cd ~
(DevStack also ships a script that does the same, tools/create-stack-user.sh, named in the message above. In the lab, the user was created at first boot by cloud-init, with the same sudo rule and its home in /home/stack instead of /opt/stack; DevStack worked fine, and created /opt/stack itself for the code and data. So these commands were not run by hand here.) Then install git if it is missing (sudo apt-get install git -y).
Step 2: get DevStack, on a stable branch
By default git clone gives you the master branch, the development version, which changes every day and sometimes breaks. For a learning environment that behaves the same tomorrow, use a stable branch, named after the release:
$ git clone https://opendev.org/openstack/devstack -b stable/2026.1 && cd devstack $ git log -1 --format="%h %cd" --date=short da2f4d7 2026-05-21 $ git rev-parse --abbrev-ref HEAD stable/2026.1 $ ls CONTRIBUTING.rst FUTURE.rst HACKING.rst LICENSE Makefile README.rst clean.sh data doc extras.d files functions functions-common gate inc lib local.conf openrc playbooks roles run_tests.sh samples stack.sh stackrc tests tools tox.ini unstack.sh
stable/2026.1 was the most settled branch on 8 October 2026 (the very new 2026.2 branch existed too). To see which stable branches exist: git ls-remote --heads https://opendev.org/openstack/devstack 'stable/*'. Before you start, check that the branch supports your operating system: its stack.sh has a SUPPORTED_DISTROS line, and the 2026.1 one lists trixie|bookworm|noble|rhel9|rhel10. If your distribution is not in it, stack.sh prints WARNING: this script has not been tested on ... and stops with "If you wish to run this script anyway run with FORCE=yes" (that override exists, per the script; it was not tried).
Step 3: local.conf
One file, in the devstack directory, controls the install. The one used here:
$ cat ~/devstack/local.conf [[local|localrc]] ADMIN_PASSWORD=practice DATABASE_PASSWORD=practice RABBIT_PASSWORD=practice SERVICE_PASSWORD=practice HOST_IP=10.0.2.15 LIBVIRT_TYPE=kvm LOGFILE=/opt/stack/logs/stack.sh.log LOG_COLOR=False LOGDAYS=1 # keep the lab small VOLUME_BACKING_FILE_SIZE=10G disable_service tempest
Line by line:
- [[local|localrc]] is the header; leave it exactly like that.
- The four passwords. ADMIN_PASSWORD is for the admin and demo users (the ones you log in with), DATABASE_PASSWORD is the database's, RABBIT_PASSWORD the message queue's, SERVICE_PASSWORD the one the services use to talk to Keystone. They are written in clear text in this file, which is fine for a throwaway lab and not for anything real. Use only letters and digits: the DevStack documentation warns that "some services fail" with special characters in a password.
- HOST_IP=10.0.2.15 is this machine's address, which DevStack puts in every service URL (look back at the catalogue on the previous page). DevStack can guess it, but with more than one network interface or address, set it yourself.
- LIBVIRT_TYPE=kvm makes instances use hardware acceleration. It ends up in Nova's config as virt_type = kvm.
- LOGFILE, LOG_COLOR=False and LOGDAYS=1 send the installer's output to a file, without terminal colour codes, and keep one day of old logs.
- VOLUME_BACKING_FILE_SIZE=10G sets the size of the file that Cinder (block storage) turns into a volume group. A sparse file: it says 10 GB but takes space only as volumes are written (24 MB at the end of the install).
- disable_service tempest leaves out Tempest, OpenStack's big test suite, which a learning cloud does not need. You turn individual services on and off the same way (enable_service NAME).
The document's own example also sets FLOATING_RANGE and FIXED_RANGE (the addresses of the outside network and of the instances' private network). The lab left them at DevStack's defaults, and the next sections show what those turned out to be.
Step 4: run it
cd ~/devstack ./stack.sh
The documentation's description is accurate: "a seemingly endless stream of activity ensues". Every command is printed with a + in front (the output of this run was about 20,000 lines), so the screen is not a progress bar, only a reassurance that something is happening. Two practical tips:
- Run it so that a dropped connection cannot kill it. In the lab: cd ~/devstack && (nohup ./stack.sh > ~/stack-run.out 2>&1 &), then read the log with tail. In a plain terminal, tmux or screen does the same job.
- Read the end of the output for the result. On success it prints a summary like the one below. The log file named in LOGFILE, /opt/stack/logs/stack.sh.log, has the full record.
When it finished, 17 minutes and 44 seconds after it started, it printed:
$ ./stack.sh (the last lines it prints) This is your host IP address: 10.0.2.15 This is your host IPv6 address: fec0::5054:ff:fe12:3456 Horizon is now available at http://10.0.2.15/dashboard Keystone is serving at http://10.0.2.15/identity/ The default users are: admin and demo The password: practice Services are running under systemd unit files. For more information see: https://docs.openstack.org/devstack/latest/systemd.html DevStack Version: 2026.1 Change: da2f4d73f5ad74fc8ecfbe15bd7e20f6b0982dbb Fix noble OVN source Zuul parent 2026-05-21 15:33:38 +0000 OS Version: Ubuntu 24.04 noble
The log also holds a timing table, which shows where the time went:
$ grep -A 22 "DevStack Component Timing" /opt/stack/logs/stack.sh.log DevStack Component Timing (times are in seconds) ========================= wait_for_service 19 async_wait 104 osc 269 apt-get 251 test_with_retry 3 dbsync 6 pip_install 138 apt-get-update 15 run_process 32 git_timed 205 ------------------------- Unaccounted time 22 ========================= Total runtime 1064 ================= Async summary ================= Time spent in the background minus waits: 362 sec Elapsed time: 1064 sec
Reading it: most of the time went into creating things in the cloud (osc, 269 seconds: users, projects, endpoints, networks, flavours, with the openstack command), installing packages (apt-get, 251), downloading source code (git_timed, 205) and installing Python packages (pip_install, 138). Starting the services took 32 seconds, and the database setup 6. About six minutes of the work happened in the background at the same time, which is why the pieces add up to more than the total.
One thing that looks alarming in the middle of the log and is not: a line starting ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. It is pip complaining about version pins between libraries, and the install finished fine. The question to ask of any message is: did stack.sh stop? If it did not, it was a warning.
Step 5: check that it works
DevStack writes a client configuration file for you, /etc/openstack/clouds.yaml, with ten ready-made logins (ordinary, admin, alternative project, read-only users and system-level users). You pick one with OS_CLOUD:
$ grep -E "^ [a-z-]+:" /etc/openstack/clouds.yaml
devstack:
devstack-admin:
devstack-admin-demo:
devstack-alt:
devstack-alt-member:
devstack-alt-reader:
devstack-reader:
devstack-system-admin:
devstack-system-member:
devstack-system-reader:
$ sed -n 1,10p /etc/openstack/clouds.yaml
clouds:
devstack:
auth:
auth_url: http://10.0.2.15/identity
password: practice
project_domain_id: default
project_name: demo
user_domain_id: default
username: demo
region_name: RegionOne
$ OS_CLOUD=devstack-admin openstack token issue -c project_id -c user_id
+------------+----------------------------------+
| Field | Value |
+------------+----------------------------------+
| project_id | 2b380a2be5ad443e92cabeae7d9909b4 |
| user_id | 8da6246745c84127a7d28b5c9810a1f6 |
+------------+----------------------------------+
$ OS_CLOUD=devstack-admin openstack service list -c Name -c Type
+-------------+----------------+
| Name | Type |
+-------------+----------------+
| nova | compute |
| nova_legacy | compute_legacy |
| keystone | identity |
| cinder | block-storage |
| glance | image |
| placement | placement |
| neutron | network |
+-------------+----------------+
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18080/dashboard/auth/login/ (on the laptop, through the forwarded port)
200
- devstack is the demo user in the demo project, the one for everyday work. devstack-admin is admin: it sees and may do everything.
- Seven services are in the catalogue, as the previous page described.
- Horizon answers (200). In the lab it was reached from the laptop through a forwarded port; its address is http://HOST_IP/dashboard, and the login is admin or demo with the password from local.conf.
- There is also the older way: source ~/devstack/openrc admin admin sets environment variables (OS_USERNAME and the rest) in your shell for one user and project. clouds.yaml is neater, because it never leaves passwords in your shell's environment.
What the installer did to the machine
Because DevStack "makes substantial changes", it helps to see them. All of this is from the lab machine after the install:
$ grep -c " install " /var/log/dpkg.log
496
$ grep -c "^Commandline" /var/log/apt/history.log
12
$ grep " install " /var/log/dpkg.log | awk "{print \$4}" | cut -d: -f1 | grep -E "^(mysql-server|rabbitmq-server|libvirt-daemon-system|qemu-system-x86|openvswitch-switch|ovn-central|ovn-host|apache2|memcached|python3-dev|targetcli-fb|haproxy)$" | sort -u | tr "\n" " "; echo
apache2 haproxy libvirt-daemon-system memcached mysql-server openvswitch-switch ovn-central ovn-host python3-dev qemu-system-x86 rabbitmq-server targetcli-fb
$ getent passwd | awk -F: "\$1 ~ /^(stack|rabbitmq|mysql|memcache|haproxy|dnsmasq|libvirt-qemu|libvirt-dnsmasq|epmd)\$/ {print \$1\":\"\$3\":\"\$6}"
stack:1000:/home/stack
dnsmasq:999:/var/lib/misc
memcache:109:/nonexistent
haproxy:110:/var/lib/haproxy
epmd:112:/run/epmd
rabbitmq:113:/var/lib/rabbitmq
mysql:114:/nonexistent
libvirt-qemu:64055:/var/lib/libvirt
libvirt-dnsmasq:116:/var/lib/libvirt/dnsmasq
$ sudo cat /etc/sudoers.d/50_stack_sh
stack ALL=(root) NOPASSWD:ALL
Defaults:stack secure_path=/sbin:/usr/sbin:/usr/bin:/bin:/usr/local/sbin:/usr/local/bin
Defaults:stack !requiretty
$ sudo du -sh /opt/stack/data /opt/stack/horizon /opt/stack/nova /opt/stack/neutron /opt/stack/cinder /opt/stack/keystone /opt/stack/glance /opt/stack/logs
1005M /opt/stack/data
234M /opt/stack/horizon
227M /opt/stack/nova
148M /opt/stack/neutron
124M /opt/stack/cinder
52M /opt/stack/keystone
32M /opt/stack/glance
2.5M /opt/stack/logs
$ df -h / | tail -1
/dev/vda1 58G 13G 46G 22% /
$ ls -ls /opt/stack/data/stack-volumes-lvmdriver-1-backing-file
24708 -rw-r--r-- 1 stack stack 10737418240 Oct 8 21:19 /opt/stack/data/stack-volumes-lvmdriver-1-backing-file
$ sudo losetup -a
/dev/loop0: [64769]:787286 (/opt/stack/data/stack-volumes-lvmdriver-1-backing-file)
$ sudo vgs --noheadings -o vg_name,vg_size,vg_free
stack-volumes-lvmdriver-1 <10.00g 484.00m
$ ip -br addr show br-ex
br-ex UNKNOWN 172.24.4.1/24 2001:db8::2/64 fe80::a827:48ff:fe5
$ ip -br addr show virbr0
virbr0 DOWN 192.168.122.1/24
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
$ systemctl is-enabled devstack@n-api devstack@keystone mysql rabbitmq-server apache2 libvirtd | tr "\n" " "; echo
enabled enabled enabled enabled enabled enabled
In plain words:
- Packages. 496 were installed, in 12 separate apt runs: a database (MySQL), a message queue (RabbitMQ), a web server (Apache), memcached, the hypervisor stack (QEMU and libvirt), Open vSwitch and OVN for networking, haproxy, a tool for iSCSI targets, and Python build tools.
- Users. A set of system accounts: rabbitmq, mysql, memcache, haproxy, dnsmasq, epmd, libvirt-qemu and libvirt-dnsmasq, next to stack.
- A sudoers file, /etc/sudoers.d/50_stack_sh: the stack user may run anything as root without a password. That is a powerful rule to leave on a machine; one more reason this is for dedicated machines.
- Source code and data, in /opt/stack: one git checkout per project (Horizon 234 MB, Nova 227 MB, Neutron 148 MB, Cinder 124 MB and so on), installed in editable mode into one Python virtual environment, and about 1 GB of data in /opt/stack/data: the Python virtual environment (765 MB), etcd's database (123 MB), Nova's and Glance's data (52 and 42 MB, including the test image).
- Storage. A 10 GB sparse file attached as a loop device (/dev/loop0) and made into an LVM volume group, stack-volumes-lvmdriver-1, which is where Cinder's volumes will go. ls -ls shows it takes 24 MB for now.
- Networking. A bridge, br-ex, with the address 172.24.4.1/24 (and an IPv6 address): that is the cloud's outside network, where floating IPs will live. A second one, virbr0, from libvirt's default network. And IP forwarding is switched on for the whole machine.
- Services. Twenty devstack@... systemd units plus MySQL, RabbitMQ, Apache and libvirt, set up to start at boot (the reboot test below confirms it).
Rebooting: what comes back by itself, and what does not
Sooner or later you will restart the machine, and the honest answer to "will my cloud survive?" is "mostly". This was tested, twice.
First reboot, with the install exactly as stack.sh left it:
$ sudo systemctl reboot (then, after the machine is back)
$ uptime -p; systemctl --failed --no-legend | awk '{print $2}' | tr "\n" " "
up 0 minutes
(no failed units listed)
$ systemctl is-active mysql rabbitmq-server apache2 libvirtd ovn-controller ovn-northd
active active active active active active
$ systemctl list-units "devstack@*" --no-legend | awk '{print $1, $4}'
(all 20 devstack@ units listed, every one "running": c-api c-sch c-vol dstat etcd g-api keystone n-api-meta n-api n-cond-cell1 n-cpu n-novnc-cell1 n-sch n-super-cond neutron-api neutron-ovn-maintenance-worker neutron-periodic-workers neutron-rpc-server placement-api q-ovn-agent)
$ sudo losetup -a
/dev/loop0: [64769]:787286 (/opt/stack/data/stack-volumes-lvmdriver-1-backing-file)
$ openstack compute service list -c Binary -c State -c Status
+----------------+---------+-------+
| Binary | Status | State |
+----------------+---------+-------+
| nova-scheduler | enabled | up |
| nova-conductor | enabled | up |
| nova-conductor | enabled | up |
| nova-compute | enabled | up |
+----------------+---------+-------+
$ curl -s -o /dev/null -w "horizon %{http_code}\n" http://127.0.0.1/dashboard/auth/login/
horizon 200
Good news first. Every service came back on its own, with nothing failed: the database, the queue, Apache, libvirt, OVN, all 20 DevStack units, Nova reporting its services up, and even the loop-backed Cinder volume group was re-attached. Horizon answered. Now the two things that did not come back:
$ ip -br addr show br-ex br-ex DOWN $ ping -c2 -W1 172.24.4.42 (the router's address on the public network) PING 172.24.4.42 (172.24.4.42) 56(84) bytes of data. --- 172.24.4.42 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1000ms $ openstack server list -c Name -c Status | flow-test | ACTIVE | $ sudo virsh list --all Id Name State ------------------------------------ - instance-00000001 shut off $ openstack server start flow-test ConflictException: 409: Client Error for url: http://10.0.2.15/compute/v2.1/servers/5207c12a-d569-4456-8f1c-c998a0aa8735/action, Cannot 'start' instance 5207c12a-d569-4456-8f1c-c998a0aa8735 while it is in vm_state active
- The br-ex bridge is DOWN with no address. The outside network no longer works: the router's address on it, 172.24.4.42, cannot be reached, which would also break every floating IP you create. The address was added by hand by stack.sh, and nothing writes it down.
- The instance is shut off, but Nova still says ACTIVE. The hypervisor's guest was gone with the reboot, and Nova's records did not notice yet. openstack server start is refused with 409 Conflict, because as far as Nova knows the server is already running.
The first fix is one command each (a quick repair; it lasts until the next reboot):
$ sudo ip link set br-ex up; sudo ip addr add 172.24.4.1/24 dev br-ex; sudo ip -6 addr add 2001:db8::2/64 dev br-ex $ ip -br addr show br-ex br-ex UNKNOWN 172.24.4.1/24 2001:db8::2/64 fe80::a827:48ff:fe5 $ ping -c2 -W1 172.24.4.42 64 bytes from 172.24.4.42: icmp_seq=1 ttl=254 time=2.94 ms 64 bytes from 172.24.4.42: icmp_seq=2 ttl=254 time=0.794 ms --- 172.24.4.42 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms
The router's address answers once br-ex has its addresses back. For the instance, the way out of the "ACTIVE but not running" state is a hard reboot, which tells Nova to rebuild the guest from its own records. The lab simulated the state by destroying the guest in libvirt, and then:
$ virsh destroy instance-00000001 (simulate what a reboot leaves behind) Domain 'instance-00000001' destroyed nova: ACTIVE | libvirt: shut off $ openstack server start flow-test ConflictException: 409: Client Error for url: http://10.0.2.15/compute/v2.1/servers/5207c12a-.../action, Cannot 'start' instance 5207c12a-... while it i... nova: ACTIVE | libvirt: shut off $ openstack server reboot --hard flow-test nova: HARD_REBOOT | libvirt: shut off nova: HARD_REBOOT | libvirt: running (a few seconds later) nova: ACTIVE | libvirt: running
start is refused, reboot --hard works: Nova shows HARD_REBOOT and then ACTIVE with libvirt running. (Left alone, Nova would also notice by itself, through a periodic power-state check, but not at once, and the 409 messages you get while it catches up are confusing.)
Neither of those is satisfying, so the permanent fixes. One is a Nova setting, resume_guests_state_on_host_boot = true, in the [DEFAULT] section of /etc/nova/nova-cpu.conf: when the compute service starts after a boot, it restarts the guests that were running. The other is a small systemd unit that gives br-ex its addresses at boot, after Open vSwitch is up:
$ sudo sed -i '/^\[DEFAULT\]/a resume_guests_state_on_host_boot = true' /etc/nova/nova-cpu.conf $ grep -n resume_guests_state_on_host_boot /etc/nova/nova-cpu.conf 3:resume_guests_state_on_host_boot = true $ cat /etc/systemd/system/br-ex-address.service [Unit] Description=Give the DevStack public bridge br-ex its addresses After=openvswitch-switch.service ovn-controller.service Wants=openvswitch-switch.service [Service] Type=oneshot RemainAfterExit=yes ExecStartPre=/bin/sh -c 'for i in $(seq 1 60); do ip link show br-ex >/dev/null 2>&1 && exit 0; sleep 2; done; exit 1' ExecStart=/bin/sh -c 'ip link set br-ex up && ip addr replace 172.24.4.1/24 dev br-ex && ip -6 addr replace 2001:db8::2/64 dev br-ex' [Install] WantedBy=multi-user.target $ sudo systemctl daemon-reload; sudo systemctl enable br-ex-address.service Created symlink /etc/systemd/system/multi-user.target.wants/br-ex-address.service → /etc/systemd/system/br-ex-address.service. $ systemd-analyze verify /etc/systemd/system/br-ex-address.service && echo "unit verifies OK" unit verifies OK
And then the second reboot, with nothing done by hand afterwards:
$ sudo systemctl reboot (nothing done by hand afterwards)
$ uptime -p; systemctl --failed --no-legend | awk '{print $2}'
up 1 minute
$ systemctl is-active br-ex-address.service; ip -br addr show br-ex
active
br-ex UNKNOWN 172.24.4.1/24 2001:db8::2/64 fe80::a827:48ff:fe5
$ ping -c2 -W1 172.24.4.42 | tail -3
--- 172.24.4.42 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1034ms
rtt min/avg/max/mdev = 0.696/0.701/0.706/0.005 ms
$ openstack server list -c Name -c Status -f value; sudo virsh list --all | sed -n 3,4p
flow-test ACTIVE
1 instance-00000001 running
$ journalctl -u devstack@n-cpu -b | grep -i "Rebooting instance after"
INFO nova.compute.manager [None req-7e7eecad-... None None] [instance: 5207c12a-d569-4456-8f1c-c998a0aa8735] Rebooting instance after nova-compute restart.
This time the machine came back with br-ex addressed, the router answering pings, flow-test ACTIVE and running (Nova's log says "Rebooting instance after nova-compute restart"), and no failed unit. This recipe is tied to this DevStack version and this networking layout (OVN with the default 172.24.4.0/24 public network): if you changed the floating range or the public network's settings in local.conf, use your own addresses in the unit.
💡 Why does DevStack not do this itself? It is a tool for building a development cloud, and the usual workflow is to rebuild, not to reboot: the documentation's own scripts for stopping and starting over are unstack.sh (stops what stack.sh started, leaving MySQL and RabbitMQ running unless you pass -a) and clean.sh (removes the data files and leaves the packages alone). Re-running stack.sh after either is the normal way to start fresh. Those were not run in this lab, because the cloud is needed for the next pages.
Where things are
/opt/stack/logs/stack.sh.log the installer's complete record /opt/stack/NAME/ the source of each project (git checkouts) /opt/stack/data/venv/ the shared Python virtual environment /etc/nova /etc/neutron /etc/keystone /etc/glance /etc/cinder each service's configuration /etc/openstack/clouds.yaml the ready-made client logins (OS_CLOUD=...) ~/devstack/openrc the older way: source it for one user and project ~/devstack/local.conf what you asked for journalctl -u 'devstack@*' every service's log, in the journal systemctl status devstack@n-api one service (n-api, n-cpu, g-api, keystone, ...)
The services are ordinary systemd units, named devstack@NAME, so systemctl restart and journalctl -u work the way you already know. (Restarting a single one was not run here.) Editing a config file under /etc takes effect after restarting that service, which is how the resume_guests_state_on_host_boot change above was made.
When it goes wrong
- "DevStack should be run as a user with sudo permissions, not root." Create the stack user (step 1).
- stack.sh stops with an error in the middle. Read the end of the log, not the beginning: the failing command is the last thing it ran. A flaky download is the usual cause; running stack.sh again (after unstack.sh) is the normal retry. (Not run here, because nothing failed.)
- Weird failures with special characters. Passwords: letters and digits only (documentation).
- Instances are very slow. No /dev/kvm and the cloud is using plain emulation. Check virt_type in /etc/nova/nova-cpu.conf, and look for /dev/kvm.
- After a reboot, nothing reaches the instances, or servers are ACTIVE but dead. The two things above.
- The machine is out of memory. The cloud is many processes; this lab used 5.4 GB at rest. A machine with less RAM than the install needs is a problem of its own that was not tested.
- Using master. If you did not pick a stable branch, the install can fail on a day when development is broken, and a tutorial written for one release will not match.
Where to go from here
DevStack is the learning install. For a cloud that has to keep running, the usual choices are packaged, container-based or Kubernetes-based installers: Kolla-Ansible (OpenStack in containers, installed with Ansible), Canonical's Sunbeam or the OpenStack-Helm project, among others. They solve what DevStack leaves out: high availability, upgrades without rebuilding, and TLS between services. None of them was run for this series.
What this page did not cover
- Using the cloud. The next page launches a real instance: image, flavor, network, key pair, security group and floating IP.
- Other hardware. A machine with less than 8 GB, a different distribution (the 2026.1 branch also lists Debian and RHEL-family systems), or a multi-node DevStack.
- Starting over with unstack.sh and clean.sh, which were read, not run.
- Upgrading a DevStack cloud, which is not what it is for.
The cheat sheet
sudo useradd -s /bin/bash -d /opt/stack -m stack the stack user echo "stack ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/stack git clone https://opendev.org/openstack/devstack -b stable/2026.1 vi ~/devstack/local.conf passwords (letters and digits), HOST_IP, LIBVIRT_TYPE cd ~/devstack && ./stack.sh the install (about 18 minutes here) tail -f /opt/stack/logs/stack.sh.log watch it OS_CLOUD=devstack-admin openstack service list is the cloud alive journalctl -u 'devstack@*' -f watch every service systemctl list-units 'devstack@*' the twenty units ./unstack.sh ./clean.sh stop / wipe (documented, not run here) resume_guests_state_on_host_boot = true in /etc/nova/nova-cpu.conf, [DEFAULT] br-ex-address.service restores 172.24.4.1/24 on br-ex at boot openstack server reboot --hard NAME the cure for "ACTIVE but not running"
← OpenStack in One Page: What It Is and How the Services Fit Together · ↑ All tutorials · Your First OpenStack Instance: Key Pair, Security Group, Floating IP and the Mistakes Everybody Makes →